Quick start
npx trooth check trooth.co # the record, its evidence state and its newest events
npx trooth profile trooth.co # the complete Trust Profile, section by section
npx trooth verify trooth.co # check Trooth's signed witness statement on your machine
Node 18 or newer. Nothing to configure.
Put your company on the Network
Trooth operates the Trooth Network: one free, public, machine-readable record per company, carrying its identity, products, commercial terms, people, documents, security and privacy posture, AI practices, procurement terms and relationships. Buyers read it instead of sending another questionnaire, and so do the AI assistants they ask.
Claim your company's record. It is free. Once it is published, npx trooth check yourcompany.com reads it, and so does every assistant connected to the Trooth MCP connector.
Trooth witnesses and dates facts. It does not grade, rate or rank anyone. Trooth signs witness statements, one for each reading it takes of a company's public surface, and, for workspaces that create a delegation, delegation receipts and action evidence references (docs/ACTION-EVIDENCE.md). What a company declares about itself is labeled declared and is not signed. The CLI prints counts, reported apart, and never adds them into one number.
What this CLI sends
trooth check <domain>sends one request toapi.trooth.cowith the domain you ask about in the request URL, plus what every web request carries: your IP address and atrooth-cli/<version>user agent. Trooth's servers can therefore see which domain you looked up; trooth.co/privacy and the retention schedule say what is kept and for how long. Nothing else about you or your machine is sent.trooth lint [path]reads what your own infrastructure declares and prints those declarations as facts. Entirely local: it opens files and opens no sockets.
Install
npm install -g trooth
trooth --version
One dependency, pinned to an exact version and locked in npm-shrinkwrap.json: yaml, the maintained YAML parser, which itself has none. It is there so Kubernetes files are parsed rather than pattern-matched. Install with --ignore-scripts if you like; neither package has an install script. Every release is published from GitHub Actions with an npm provenance statement tying the tarball to its tagged commit.
Commands
Flags
--json is the flag every command takes; profile also takes --section and --markdown, lint takes --allow-incomplete, verify takes --file, --keys, --mapping, --manifest, --offline, --save-bundle, --bundle, --no-log and --log-vkey, log monitor takes --state, log checkpoint and log monitor take --witnesses, log receipt takes --out, every log command takes --log-vkey, public-record takes --cik, --lei, --ticker and --log-vkey, mcp-tools takes --live and --log-vkey, declare takes --domain and --key, declare sign also --record, --bind-record, --days, --out and the repeatable --product, --api, --repo and --add-key, and declare check takes --file, --domain, --no-dns and --no-record, evidence verify takes --keys, --online, --protocol, --reference, --log and --log-index, and delegation verify takes --keys, --delegation, --log and --log-index. With --json, stdout carries exactly one JSON document and nothing else, and every diagnostic goes to stderr. On an error the document is {"ok": false, "error": "...", "exit": N}; a non-2xx response adds http_status, and lint with nothing to read adds files_opened. --help and --version print plain text whether or not --json is given.
public-record also takes --timeout <seconds> (default 45), mirror takes --from and --check, and guard takes --policy, --tool, --host, --args, --cache, --max-age, --offline, --base, --witness, --timeout-ms and --log-vkey. Every command takes --help and -h.
Any other flag is a usage error. The message names the flag, lists the flags the command takes, and points at trooth <command> --help.
Exit codes
Every command's own codes are listed by trooth <command> --help. The table below is all of them.
guard hook follows Claude Code's codes instead of these: 0 for a tool the policy does not cover, an allow, or a hold (the ask JSON on stdout), and 2 for a deny or any failure, because Claude Code lets a tool run on any other code (docs/GUARD.md section 8).
A company with no record, a listed company without a witnessed reading, and a Network that could not be read are different answers, so they exit differently. A pipeline can tell them apart without parsing prose, and a Trooth outage never reads as a company with no record.
trooth check
trooth check trooth.co
Example output. The values are illustrative; the shape is what the binary prints.
Trooth Network // public record · read-only //
Trooth, LLC trooth.co
Listing state: listed; Trooth witnessed a reading reading dated 2026-09-26 first published 2026-08-01
Live probes: 65 read; 63 as expected
Self-attestations: 35 asked; 27 attested
Live probes are readings Trooth took itself, from the company's public surface.
Self-attestations are what the company attested about itself; Trooth records them
and did not witness them. The two are reported apart and never added into one number.
Badge rw_... Key trooth-master-2026-09
Latest ledger events, newest first
• 2026-09-26 live probes re-read
• 2026-09-26 reading completed
• 2026-08-01 record published to the Trooth Network
--json carries the whole ledger, with the feed's own wording for each event.
This command did not check the record's signature. The signature covers the
reading, not every fact on the company's profile. To check it yourself: https://trooth.co/docs/verifiable-evidence
A dated, point-in-time record. Trooth issues no verdict and no single number.
Full record: https://trooth.co/network/trooth.co · Signing keys: https://api.trooth.co/public/keys
The line that begins Listing state: gives the listing and evidence state, decided from the record's own fields and never from a matching name:
The two counts use the same form as the record page on trooth.co: live probes (how many were read at the last reading, and how many of those returned the expected result) and self-attestations (how many the company was asked for, and how many it attested). Live probes are readings Trooth took itself; self-attestations are the company's statements about itself, which Trooth records and does not witness. The counts come from the directory record; they are the counts of the signed reading, and not any other total a page may show.
The events section prints the three newest entries in the record's ledger, newest first, by timestamp; entries with the same timestamp keep the feed's order, the later entry first. Known event types get a plain label: scan_completed prints as "reading completed", standing_published as "record published to the Trooth Network" and rewitnessed as "live probes re-read". A type the CLI does not know prints with its underscores turned into spaces. --json carries every event the feed returns, with its type and detail exactly as the feed has them.
What check accepts. A bare domain or a URL. It is parsed with the standard URL parser, so case, a trailing dot, the default port (80 or 443), a path and an internationalized name (converted to its ASCII form) normalize to one domain, and a leading www. is dropped: https://Trooth.co:443/path reads trooth.co. A URL with a user name or password, a non-default port, an IP address, a scheme other than http or https, or a name with no dot that no Trooth record carries as its slug is a usage error (exit 2) rather than a guess.
Domain or slug. check, verify and public-record also take a Trooth slug, as the MCP connector and the A2A agent do: a bare name with no dot, such as trooth. It is resolved on the record projection (GET https://trooth.co/api/network/profile?q=<slug>&contract=2, one extra request) to the domain of the record that carries exactly that slug, which stderr names (trooth is a Trooth slug: the record for trooth.co), and the command then runs on that domain. A record the projection finds by name rather than by slug is offered as a suggestion, never used; a slug no record carries, or one that names more than one record, is exit 2 with the domain to pass when there is one; a projection that cannot be read is exit 3. A domain is never read as a slug. verify --offline and verify --bundle cannot resolve a slug and take a domain only, and declare check and guard take domains only, because they read the site or the host itself.
How check asks. One GET /directory/api/vendors/<domain>, with a 15-second deadline (TROOTH_TIMEOUT_MS changes it), a 1 MiB limit on the body, redirects refused, a JSON content type required, and at most one retry, only after a connection failure or a 502, 503 or 504. Up to 0.4.4, a plain-text 404 made the CLI download the whole directory list and search it; the single-record route has been served since 2026-09-26, and 0.5.0 has no fallback, so an unexpected answer is a service error, never "not listed".
For scripting:
trooth check trooth.co --json
{
"domain": "trooth.co",
"listed": true,
"state": "listed_witnessed",
"company_name": "Trooth, LLC",
"witnessed_at": "2026-08-30T00:00:00Z",
"first_published_at": "2026-08-01T00:00:00Z",
"badge_id": "rw_...",
"probes": { "passed": 64, "total": 65 },
"attested": { "passed": 27, "total": 35 },
"events": [
{ "type": "scan_completed", "at": "2026-08-01T00:00:00Z", "detail": "65 probes read · 64 returned the expected result · 27 declarations recorded · reading signed" },
{ "type": "standing_published", "at": "2026-08-01T00:00:00Z", "detail": "point-in-time · published to the Trooth Network" },
{ "type": "scan_completed", "at": "2026-08-30T00:00:00Z", "detail": "65 probes read · 64 returned the expected result · 27 declarations recorded · reading signed" },
{ "type": "standing_published", "at": "2026-08-30T00:00:00Z", "detail": "point-in-time · published to the Trooth Network" }
],
"receipt_signature": "...",
"authority_key_id": "trooth-master-2026-09",
"signature_checked": false,
"verify_keys": "https://api.trooth.co/public/keys",
"verify_how": "https://trooth.co/docs/verifiable-evidence",
"record_url": "https://trooth.co/network/trooth.co"
}
In probes, total is how many live probes were read at the last reading and passed (the API's field name) is how many returned the expected result. In attested, total is how many declarations were asked for and passed is how many the company attested. witnessed_at is the date of the witnessed reading, and is null unless state is listed_witnessed; it is never a profile edit time. events is the whole ledger, oldest first, in the feed's order. Each event's detail is written when the event is stored and is not rewritten afterward, so older and newer events of the same type can be worded differently.
category and description are added when the record carries them. Those are the only fields check --json emits: anything else the feed happens to carry is dropped on the way out, so a script written against this shape keeps working. A field whose name contains score, tier, grade, rank, rating, level or percent, or is rate, is dropped no matter what the feed sends.
A company with no record exits 1 and emits {"domain": "...", "listed": false, "state": "not_listed", "record_url": "..."}. That is not a judgment. It means the Network's public feed carries no record for that domain. A company gets a record at trooth.co/get-started, free.
receipt_signature is Trooth's Ed25519 signature over the directory receipt and authority_key_id names the key that made it; the public keys are listed at trooth.co/verify/keys. signature_checked is always false: this CLI does not check any signature, and nothing it prints should be read as a checked signature. The object whose exact signed bytes Trooth publishes is the reading's witness statement, which is not part of this feed; trooth.co/docs/verifiable-evidence shows how to fetch and check it offline. That signature covers the reading. It does not cover the company's own declarations, which are not signed by anyone.
trooth profile
trooth profile trooth.co # every section of the Trust Profile
trooth profile trooth --section security,privacy # a slug works too; two sections
trooth profile trooth.co --json > profile.json # one JSON document
trooth profile trooth.co --markdown > profile.md # the same, as Markdown
profile prints a company's complete Trust Profile, section by section, from the same single request check sends: GET https://trooth.co/api/network/profile?q=<domain>&contract=2 (TROOTH_WEB overrides the base). No key, no account. A bare name with no dot is read as a Trooth slug and resolved to its domain first, as for check.
Every section the record carries is printed, in the page's order, with its state and who said it, and then each fact with its value, who said it (the company, Trooth's own observation, a named public source) and its date when Trooth recorded one. A fact the record does not date prints date unknown: no date is borrowed from the record's update time, a reading's time or another fact. A section the company left empty prints not published, a section shared on request only says so, and a section Trooth could not read on that load says it is unavailable, not empty. Notes the record attaches to a section, group or item are printed with it. A fact two sources disagree about is marked contested and every account is listed; Trooth does not choose between them.
The profile is the company's own declared record, and it is not signed. Facts Trooth observed are labeled as such, but nothing in the profile carries a signature. What Trooth signs about a company's record is the witness statement for a reading it took of the company's public surface; trooth verify <domain> checks it on your machine. (Trooth also signs delegation receipts and action evidence references for workspaces that create a delegation; they are not part of a company's profile.) The output says this at the top, in every form.
Trooth trooth.co slug trooth
Trust Profile: https://trooth.co/network/company/trooth
record updated 2026-10-06T01:08:12.526Z · record version 8 · read 2026-10-09T20:56:41.921Z
This profile is the company's own declared record, as Trooth publishes it; facts Trooth observed are labeled as such. It is not signed: what Trooth signs about a company's record is the witness statement for a reading it took of the company's public surface, and that statement covers the reading, not these facts.
Check that statement yourself: trooth verify trooth.co
== Commercial & pricing (pricing) · published · the company (declared)
Declared by the company. Trooth publishes it as written and does not certify it. Cite it as the company's own statement.
Free plan or trial: Fully free
said by the company (declared) · date unknown
...
List price
List price: USD 0 for every account
said by the company (declared) · declared 2026-10-06 · pricing.list-price
== Customer proof (proof) · not published · named customers
No customer endorsements have been published. ...
not published
Section names
--section takes one or more names, comma-separated or with the flag repeated; the output keeps the page's order. The names are the ones the MCP tool trooth_public_trust_profile takes, and they are the record's own section ids (profile.sections[].id in the contract 2 body). The record's typed facts (facts[], keyed <category>.<field>, the only facts that carry a date) are shown in the section their category belongs to, and each category is also accepted as a section name:
A row of the profile is joined to a typed fact only when the two have the same label and the same value in the same section; only then does the row carry the fact's key, origin, source and date (claim.declaredAt for a company declaration, else claim.observedAt). A typed fact the page does not show is added to its section under "Other recorded facts", so nothing the record carries is dropped; one whose category is in no row above is printed in a final unsorted section. A record whose facts run past one page is read to the end from the same record version, or not at all (exit 3).
--json and --markdown
--json prints one document, described by schemas/profile-output.v1.schema.json (types ProfileOutput, ProfileSection, ProfileFact in types/trooth.d.ts, the Pydantic models and the Go package):
{
"subject": "trooth.co",
"query": "trooth",
"contract": 2,
"read_at": "2026-10-09T20:56:41.921Z",
"found": true,
"signed": false,
"verify_with": "trooth verify trooth.co",
"record": { "url": "https://trooth.co/network/company/trooth", "version": 8, "digest": "sha-256=…", "updated_at": "2026-10-06T01:08:12.526Z" },
"sections": [
{
"name": "pricing", "title": "Commercial & pricing", "state": "published",
"provenance": "company_declared", "said_by": "the company (declared)",
"facts": [
{ "label": "List price", "value": "USD 0 for every account", "group": "List price", "item": null,
"provenance": "company_declared", "said_by": "the company (declared)", "key": "pricing.list-price",
"origin": "company-declared", "source": "https://trooth.co/network/company/trooth",
"date": "2026-10-06T01:08:12.375Z", "date_kind": "declared", "contested": false, "accounts": [], "signed": false }
],
"notes": [{ "group": "List price", "item": null, "text": "Scope not stated" }]
}
]
}
(abridged: the document also carries withheld, name, slug, claim_url, requested_sections, profile_present, and each section its meaning and url). date is null where the text says "date unknown". With no published record the document has "found": false, empty sections and the claim_url the API gave, or null. --markdown prints the same profile as Markdown: a heading per section, a bullet per fact with who said it and its date. --json and --markdown together are a usage error.
Exit codes: 0, a published record was found and printed; 1, no published record (an honest absence, which says nothing about the company; the claim link is printed when the API gives one); 2, a usage error (not one domain, no record with that slug, an unknown section or flag); 3, Trooth could not be read or answered something that is not the record asked for; 6, the record is withheld while a report about it is reviewed; 7, the output was not delivered.
trooth lint
lint reads what your infrastructure declares and reports it. It does not judge it. There is no verdict, no threshold, no severity and no rating, and nothing is checked against a named standard or regulation. Declaring public ingress is not a failing: a load balancer is supposed to be public. What the facts mean is your decision.
trooth lint ./infra
Output for the small fixture in this repository (trooth lint tests/fixtures/infra, trooth 0.5.0):
trooth lint // local · offline · declarations only //
tests/fixtures/infra 2 declaration file(s) read
terraform 1 · kubernetes 1
Declared
Regions and zones us-east-1
Storage declarations 1
declaring encryption 1
declaring encryption off 0
declaring nothing about encryption 0
set by an unresolved expression 0
Logging declarations 0
Identity declarations 0
Open to any address (0.0.0.0/0, ::/0) 1
Marked public 0
Inline credential literals 0
Most declared resource types
1 aws_s3_bucket
1 aws_s3_bucket_server_side_encryption_configuration
1 aws_security_group_rule
1 Deployment
Coverage complete
2 selected: 2 read, 0 not declarations, 0 excluded, 0 skipped, 0 invalid, 0 unreadable.
Facts digest sha256:2ba1da28be9e97e1e8be1f7e41641288bc632192c062ba1fbd563350448fd5a8
A SHA-256 over the counts above, in canonical form. It is an aggregate: two different
trees with the same counts share it. It does not identify your files, your repository
or a deployment.
How this was read
Every file is parsed; a file that does not parse is reported as invalid, not read.
Comments count for nothing. Nothing is evaluated: a setting that depends on a variable,
a local, a module or a function is reported as unresolved. Dockerfiles: ENV and ARG only.
Counts of what the files declare. Not a judgment: a public load balancer is
supposed to be public. Trooth issues no verdict here and checks nothing against
any standard. Nothing left this machine: lint opens files and opens no sockets.
Publish what you choose on your record at https://trooth.co/dashboard.
The bucket's encryption configuration is a setting on the bucket, not a second store, so the fixture has one storage declaration, and that one declares encryption.
How each source is read. Every file is parsed, and a file that does not parse is reported as invalid, never as read.
Nothing is evaluated. A setting that depends on a variable, a local, a module output or a function is reported as unresolved and is never counted as declared. Storage, logging and identity are counted by a resource's type or a document's kind, never by the words around it. Counts are of parsed values, not lines: two credential literals on one minified line are two.
Encryption, per store. Each storage declaration is counted in exactly one of five states:
A declaration is what a file says, not what a cloud account does: a provider default, an account-wide setting or a module can encrypt a store whose file declares nothing, and lint cannot see any of those.
Coverage. Every file the walk selects (.tf, .tf.json, .json, .yaml, .yml, Dockerfiles) ends in exactly one bucket: read; not applicable (a JSON or YAML file that is not a plan or a manifest); excluded by a stated rule (a templated manifest containing {{ }}, which has to be rendered first); skipped (over 4 MiB); invalid (did not parse); or unreadable (a permission or I/O error). The walk visits directories depth first with entries sorted by name, skips node_modules, .git, .terraform and the other build and dependency directories listed in the source, and stops at 5,000 selected files. A read with anything skipped, invalid or unreadable, or a truncated walk, is incomplete: the output says so, --json carries a coverage object with each count and a coverage_details object listing up to 50 paths per bucket with the reason (never file contents), and the exit code is 4 unless you pass --allow-incomplete.
Nothing leaves the machine. No line, no code and no value is printed or transmitted, only the path you gave it, counts, resource type names, region strings and, for files that could not be read, their paths and the reason. The credential count is a count: the literal it found is never shown.
The facts digest (facts_digest, also emitted as digest until 0.6) is a SHA-256 over the facts object in canonical form. It is an aggregate: two different trees with the same counts produce the same digest, and a release that changes how something is counted changes the digest of the same tree. It does not identify file contents, a repository, a commit or a deployment, and it is not an attestation of any of them. It is useful for noticing that the counts changed between two runs of the same version.
# Keep the fact document as a build artifact.
trooth lint --json > trooth-lint.json
In GitHub Actions
Run lint as an ordinary step. Pin one exact version, so a new release never changes a run you did not review:
- uses: actions/checkout@v4
- run: npx --yes [email protected] lint ./infra
lint makes no requests, so the step needs no token, no secret and no permission beyond contents: read for the checkout. The step's result follows the exit codes above: --json writes the fact document for a later step or an artifact, and --allow-incomplete keeps a run passing when a file cannot be read.
Environment
There is no API key. The binary asks for no credential of any kind and has no write path.
From an AI assistant
The same public Network powers Trooth's read-only MCP server, so ChatGPT, Claude, Cursor or any MCP client can ask about a company in plain words:
https://api.trooth.co/public/mcp
Ten read-only tools on public data, no key: trooth_resolve_company, trooth_search_companies, trooth_list_companies, trooth_public_trust_profile, trooth_get_fact, trooth_company_changes, trooth_ai_data_use_disclosures, trooth_outside_in_read, trooth_verify and trooth_ask. An eleventh, trooth_my_company_record, reads a signed-in company's own record with an OAuth access token from the authorization server the server's protected-resource metadata names; no scope is required, and the workspace is linked when that person signs in to trooth.co once with Continue with enterprise SSO. The pattern is written up at trooth.co/docs/agents.
trooth verify
$ trooth verify trooth.co
trooth.co witness statement v2, read 2026-10-06
signature valid (Ed25519, key trooth-master-2026-09, active)
subject trooth.co, the domain asked about
mapping matches (check mapping 1.0.1, sha256:6fea8a0f…)
manifest matches (100 entries, sha256:726b6edc…)
counts hold (65 read, 65 as expected, 35 not read)
Checked. A valid v2 signature shows that Trooth's key signed these outcome bytes together with the digest of the exact check mapping, the evaluator version, the subject scope and the digest of the evidence manifest. It does not establish the company's identity, an independently established time, or anything the reading did not read.
It reads three things: the record (trooth.co/api/network/profile), the key list (api.trooth.co/public/keys) and the check mapping the statement names (only from trooth.co/standard/check-mapping/). Then it decides for itself. Every result names the statement by trooth:statement:<sha256 of the payload bytes> (docs/IDS.md).
To keep what you checked, and check it again later with no network at all:
trooth verify trooth.co --save-bundle trooth.co.bundle.json
trooth verify --bundle trooth.co.bundle.json
A bundle carries the statement, the evidence manifest, the key list with the time it was read, and the exact mapping bytes. It is only as fresh as its key list. You can also save the inputs separately and run trooth verify <domain> --file profile.json --keys keys.json --mapping 1.0.1.json --offline.
verify exits 0 when the statement is checked, 1 when the domain has no published record (there is nothing to check), 2 for a usage error, 3 when the record, the key list or the mapping cannot be read, 4 when it is only partially checked (a mapping or manifest not supplied), 5 when the record carries no signed statement, 6 when the record is withheld, 8 when the statement is not trusted, 9 on a mismatch and 10 when it is superseded (docs/VERIFY.md section 5).
The witness statement log
Every statement Trooth publishes, and every correction it issues, is entered in one public append-only log (docs/LOG.md): an RFC 9162 Merkle tree with a C2SP checkpoint signed by a log key that signs nothing else (docs/KEY-CEREMONY.md). trooth verify asks the log for the statement and adds a line:
log included, entry 41 of a signed tree of 42 (log key pinned in trooth 0.9.0)
If Trooth has signed and logged a correction withdrawing or replacing the statement, the verdict is Superseded and the exit code is 10. Anyone can check the log only ever grows:
trooth log monitor --state trooth-log-state.json
This repository runs that check every hour (.github/workflows/log-monitor.yml).
The log asks independent witnesses of the witness network to cosign each checkpoint (docs/LOG.md section 7): a witness cosigns only after checking the new tree extends the last one it saw, so a cosigned checkpoint cannot have been shown to you while a different history was shown to someone else. trooth log checkpoint says which of the witnesses pinned in this release cosigned, and --witnesses <n> makes it exit 9 unless at least n did. Today no witness follows the log yet, so every checkpoint carries no cosignature and any --witnesses above 0, --witnesses 2 included, exits 9. The flag is there for when the witness network follows the log; until then, leave it out (or use --witnesses 0) in anything that must pass.
For SCITT tooling each entry also has an RFC 9942 COSE receipt, checked against the log key published at https://api.trooth.co/.well-known/scitt-keys:
trooth log receipt 0 --out entry-0.cose
In your own code
The same checks, passing the same test vectors, in three languages:
Delegation receipts and action evidence references: import { verifyEvidence, verifyDelegationReceipt } from 'trooth/evidence' (types included; docs/ACTION-EVIDENCE.md section 5).
schemas/ holds JSON Schema for every document involved (statement, payload v1 to v3, key list, evidence manifest, bundle, result, log receipt, correction, public record), each served at https://trooth.co/schemas/<file>, with TypeScript, Pydantic and Go types generated from them.
A checked statement means Trooth's key signed that reading for that domain. It does not mean the company is safe, compliant or authorized for anything; what to do with it is your decision.
trooth public-record
$ trooth public-record apple.com
apple.com public record, read 2026-10-07
site names Apple Inc. (https://apple.com/)
entity trooth:entity:lei:HWUPKR0MPOU8FGXBT394 Apple Inc. (the LEI binding is corroborated and so is the SEC filer binding)
SEC filer CIK 0000320193 corroborated the 10-K filed 2025-10-31 declares its extension taxonomy under www.apple.com
LEI HWUPKR0MPOU8FGXBT394 corroborated the LEI record and the SEC filer 0000320193 name the same entity, and the filer's own filing ties it to apple.com
filings 10-K 2025-10-31 · 10-Q 2026-07-31 · 8-K 2026-07-30 · DEF 14A 2026-01-08
revenue 416,161,000,000 USD (year ending 2025-09-27, 10-K filed 2025-10-31)
certificates 5 unexpired for apple.com in CT logs · issuers Apple · soonest expiry 2026-10-20
security.txt https://security.apple.com (expires 2027-07-10)
sanctions no OFAC SDN entity with the name Apple Inc.
signature signed by trooth-master-2026-09, and entry 12 of the log
Every fact carries the URL of the regulator or registry it came from (--json), and every response read is listed with its SHA-256. A filing is the company's own statement to its regulator, not Trooth's finding; a sanctions entry with the same name is not an identification. The statement naming the reading is checked here: the reading's RFC 8785 SHA-256, the signature and its key on api.trooth.co/public/keys, and its entry in the log (docs/EVIDENCE.md section 5). The signature says what Trooth read and when, not that the sources are right. A reading is cached for a day. A first reading of a domain is taken live from every source and takes 20 to 40 seconds, so public-record waits up to 45 seconds for each request (--timeout <seconds> changes it, from 1 to 600; TROOTH_TIMEOUT_MS sets it in milliseconds when --timeout is not given), and on a terminal it prints a note on stderr after 5 seconds saying a reading is in progress (TROOTH_PROGRESS=1 prints it anywhere, TROOTH_PROGRESS=0 never). A reading that does not finish in time is exit 3; one that finishes is cached, so running the command again a minute later usually answers at once.
Exit 0 when the reading names at least one SEC filer (CIK) or LEI for the domain, whatever its status (corroborated, claimed by the site, contradicted). Exit 1 when it names neither: the rest of the reading is still printed, and it says nothing about the company (a private company files nothing with the SEC). Exit 2 for a usage error, including a request the service refuses as malformed (HTTP 400); 3 when the service cannot be read, does not answer in time, is rate limited (HTTP 429), or answers with something that is not a reading of the domain; 8 when the statement's signature or key does not hold; 9 when the reading is not the one its statement names or its log entry does not check. A reading that carries no signature does not change the exit code; the signature line says why.
trooth mcp-tools
$ trooth mcp-tools https://api.trooth.co/public/mcp --live
https://api.trooth.co/public/mcp trooth-mcp 1.4.1, read 2026-10-07
tools 8 · manifest fedd1d2372462de065ba6046e45eac33c97ad628d13c910d3bcc2f8b8054e058
trooth_ask description c4fd0e4105e1… definition aa35823cf04d…
…
signature signed by trooth-master-2026-09, and entry 14 of the log
live the server lists the same tools now, byte for byte (8 tools)
An agent decides what a tool does from the description the server lists, and a server can change it after it was reviewed. Trooth reads the tool lists of MCP servers once a day with no credentials, hashes each tool's description and its whole definition, and signs and logs a statement naming the manifest whenever it changes, so the log is each server's history of tool changes. This command recomputes every hash, checks the statement and its receipt, and with --live compares the server's tools now with what Trooth logged: exit 9 when they differ. A hash says what the server listed, not what a tool does.
trooth evidence verify and trooth delegation verify
trooth evidence verify evidence.jws # the JWS, or the API's JSON holding it
trooth evidence verify authorization.json --keys keys.json --json # fully offline, one JSON document
trooth evidence verify evidence.jws --online # also: issued, redeemed, expired or revoked?
trooth evidence verify evidence.jws --protocol ap2 --reference "$MANDATE_ID" # bound to this mandate?
trooth delegation verify delegation.json # the create response: receipt and scope
trooth evidence verify authorization.json --log # also: is its commitment in the public witness log?
When a workspace delegates a bounded authority to an agent, Trooth signs a delegation receipt for each event (created, revoked), and, for each action it authorizes under the delegation, a ten-minute action evidence reference the agent attaches to the payment partner's mandate. Both are Ed25519 compact JWS signed with Trooth's evidence key (kid trooth-evidence-2026-10, purpose signed-references on api.trooth.co/public/keys). These commands check them on your machine and print what they say. Nothing a payload names is fetched: the key list (unless --keys) and, with --online, GET https://api.trooth.co/public/action-evidence/<jti> are the only requests. A reference is evidence beside the mandate: it does not move money, does not say a payment will be made, and lists in its responsibility boundary what it does not establish. The objects, the claims and every rule are in docs/ACTION-EVIDENCE.md; the decoded payloads are described by schemas/action-evidence.v1.schema.json and schemas/delegation-receipt.v1.schema.json.
Exit codes: 0, it checks; 1, --online and Trooth does not know the reference; 2, usage (no file, not a compact JWS); 3, the key list or the state could not be read; 8, not trusted (malformed, not EdDSA, kid not on the list or not signed-references, key not trusted, signature does not verify); 9, mismatch (the signature verifies, but typ, format, the claims, the lifetime, the mandate or the delegation digest do not hold); 11, evidence verify: expired; 12, evidence verify --online: redeemed or revoked. A delegation receipt does not expire; an expired delegation is reported and exits 0.
--log (both commands) reads https://api.trooth.co/scan/log/v1/entries/<N>, N from the input JSON's log.index (or receipt_log.index) or --log-index N, and checks the commitment there: signed with the same key, typ trooth-log-commitment+jwt, the right kind, and its sha256 equal to the SHA-256 of the JWS just verified. It prints logged at index N, or exits 9 (no such entry, another kind or another digest), 8 (the commitment is not signed by a trusted key) or 3 (the log could not be read). The log holds only that commitment, never the receipt or reference, so nothing about the action, the parties or the amount is public. Library: verifyLogCommitment(commitmentJws, originalJws, keys) and commitmentFromLogEntry(entry).
Release notes
Every release is in CHANGELOG.md and on GitHub Releases.
The Trooth Network is Trooth's only product. This CLI, the public API and the MCP server are interfaces to that one record, not separate products.
Security
Report a vulnerability through the Vulnerability Disclosure Policy. Nothing in this CLI takes a credential, so there is no key to leak from it. Reproduce the published tarball with npm pack at the tagged commit and compare its SHA-512 with the integrity value npm view trooth@<version> dist.integrity prints.
Links
- The Network: trooth.co/network
- This CLI on the site: trooth.co/cli
- API reference: trooth.co/docs/api
- Developers: trooth.co/developers
- Claim your company's record, free: trooth.co/signup
- Contact: trooth.co/contact
License
Apache License 2.0. See LICENSE.
The same license applies to this repository, to the trooth package on npm and to the copyright header in bin/trooth.mjs. They are meant to agree. If you find that they do not, that is a defect: please report it at trooth.co/contact.
Trooth signs what it witnessed. It never signs on a company's behalf.