Reference · Contract 2 · Read only · No key
Every public fact on Trooth is structured, attributable, dated, and retrievable. That is what makes it useful to a security reviewer today and to an AI procurement agent in the future: the same record, readable by both.
The Trooth application programming interface (API) answers questions about software and AI companies from what has been published about them. Every public endpoint is a read over an encrypted connection, answers in machine-readable JavaScript Object Notation (JSON), and needs no key and no account.
Each answer carries its own limits: where a fact came from, when Trooth last read it, and what Trooth did not read. No response contains a score, a rating or a rank for a company, and the published schema refuses one.
There is no write endpoint, no test environment, no software development kit (SDK) and no credentialed public API. Each of those is stated again where it matters below, with what to do instead, because an integration planned around something that does not exist fails late and expensively.
| Address | What it serves |
|---|---|
https://trooth.co | Every endpoint in this reference |
https://api.trooth.co/public/mcp | The Model Context Protocol server |
https://api.trooth.co/public/trust/:slug | The trust service's own read, contract at /openapi.yaml |
# Nothing to install.curl --versionnpm install -g troothThis list is deliberately short: if it is here, it works now. Anything not listed is not live, and Trooth does not advertise endpoints it has not shipped.
npx trooth check trooth.coYour first working call, in about five minutes
Any client that can make a request over an encrypted connection. For the samples, Node.js 18 or later, or Python 3.8 or later. There is no account to create and no key to request.
Send GET /api/network/profile with a domain in q. Trooth's own record is on the Network, so q=trooth.co is a query with an answer to test against.
A company the Network does not hold is 200 with { "found": false }. That is an answer. A 503 is not: it means Trooth could not read its own record, and it says nothing about the company. Keep the two apart in your code from the first line.
Every entry in facts carries origin: what the company declared, what Trooth witnessed, or what a public source says. Show it next to the value. It is most of what the fact is worth.
Send Trooth-Contract: 2 so a future change of meaning cannot reach your code unannounced. The response names the contract that served it in the same header.
That is a working integration. Next, walk the directory with the cursor, or follow a profile for changes. Before a decision depends on it, read the production checklist.
curl -i "https://trooth.co/api/network/profile?q=trooth.co" \ -H "Trooth-Contract: 2"The public application programming interface (API) needs none. Every endpoint in this reference reads records that are already published, so there is no key to request and no header to set.
Trooth does have authenticated endpoints. They are the ones this product uses to run the company and buyer workspaces, they are session scoped, and they are deliberately not offered as a public API. A documented internal route map helps an attacker and promises an integration that is not supported. When there is a credentialed API worth building on, it will be published here with scopes.
# No key, no account, no header required.curl "https://trooth.co/api/network/profile?q=example.com"Trooth-Contract: 2Errors are JSON with an error field. Status codes carry the meaning:
400 the request was malformed. The body says which field.404 the Network holds no such record. This is an answer, not a failure, and it is the correct response to a company Trooth has never seen.429 a rate limit. Honor Retry-After when the response carries one. When it does not, pause and retry once.502 and 503 Trooth could not read its own upstream. The body says so in words, and it is never a statement about the company you asked for. Retryable, with a bound.5xx ours. Retryable, with the same bound.Three rules, and the third is the one that gets left out.
429, 502, 503 and the rest of 5xx. A 400 and a 404 are answers: the same request gets the same answer for ever, and repeating it is a loop with a network call in it. A refused cursor is the one to watch, because it arrives in the middle of a paging loop that is already retrying by construction. Start again from the first page, once.503 means Trooth could not read; it does not mean the company has no record, and writing it down as one publishes our own gap as a fact about a named company. Leave the last good answer in place, or show that the read failed.| Code | Meaning | Retry |
|---|---|---|
| 200 | The answer. On a profile read it can be { found: false }. | No |
| 400 | The request was malformed. The body names the field. | No |
| 404 | The Network holds no such record. An answer. | No |
| 429 | A rate limit. Honor Retry-After when present. | Yes, bounded |
| 502 | No company in the request could be read. | Yes, bounded |
| 503 | Trooth could not read its own record. Says nothing about the company. | Yes, bounded |
{ "error": "profile_read_unavailable", "message": "The published profile record could not be read on this load. That is not the same as this company having published none, so no answer is given."}const RETRYABLE = new Set([429, 502, 503]);async function read(url, attempts = 4) { for (let i = 0; i < attempts; i++) { const res = await fetch(url); if (res.ok) return res.json(); // 400 and 404 are answers. Sending them again gets the same answer. if (!RETRYABLE.has(res.status) && res.status < 500) return { status: res.status, body: await res.json() }; const header = Number(res.headers.get("Retry-After")); const wait = Number.isFinite(header) && header > 0 ? header * 1000 : 500 * 2 ** i; await new Promise((r) => setTimeout(r, wait)); } // An outage is not a finding: surface it, do not record "no record". throw new Error("Trooth could not be read. Nothing is known about the company from this.");}Limits are per IP address and run two windows at once, one by the minute and one by the hour. Over either, the response is 429 with a JSON body carrying a plain message. Of the operations in this reference, comparison is the only one this site rate limits itself: 20 requests a minute and 200 an hour from one address. Those figures are read from the constant the route enforces, so this sentence and the limit cannot disagree.
Read the header before you decide how long to wait. Some answers carry Retry-After and some do not, and it varies by route rather than by status code. GET /api/public-mesh/{handle} sends one with its 503, and some of the credentialed workspace routes send one with their 429. The 429 from comparison does not. Where the header is present it is an instruction and the number in it is the wait. Where it is absent, back off yourself and cap the attempts, as the errors section sets out.
Comparison is the tightest of the reads because it fans out. Cached reads are generous: the directory and typeahead sit behind a sixty second edge cache, company records behind one hundred and twenty seconds, and a legal document behind an hour, so a well-behaved client mostly never reaches the origin.
| Surface | Limit | Over it |
|---|---|---|
GET /api/compare | 20 a minute and 200 an hour, per IP address | 429, no Retry-After |
| Every other read on trooth.co | No per-route limit. Served from an edge cache. | Not applicable |
GET api.trooth.co/public/trust/:slug | None | Not applicable |
| MCP tools/call | 120 a minute per IP address, counted per server instance | 429 with Retry-After: 60 |
HTTP/1.1 429 Too Many RequestsContent-Type: application/json{ "error": "Too many requests. Wait a minute and try again; if it is still refused, the hourly limit has been reached and resets within the hour." }Use the cursor. Send limit, then pass the next_cursor the response returns back as cursor. When next_cursor is null you have the last page. A cursor that cannot be read answers 400 with a sentence, rather than an empty page that looks like the end of the list.
One key decides the order, and the cursor carries that key. It is built from four fields in this sequence: whether the company is witnessed, then when it was last witnessed with the most recent first, then the lowercased name, then the company's own identifier. The identifier is not decoration. Two companies can share a name, and a sort that leaves a tie unresolved still sorts correctly while making "everything after this row" unanswerable. The last field is unique, so the order is total and every row has exactly one position. That key is in lib/directory-rank.ts, and the comparator the list is sorted with is derived from it rather than written a second time.
It prevents the offset failure, which is real and is why the cursor exists. An offset counts rows. The network grows while you are reading it, so two companies listed between your first page and your second shift everything down: you receive two rows you already had and never see two others, and nothing about the response tells you. A cursor names a position instead, and inserting a row above your position does not change which keys sort after yours.
It is not a snapshot, and there is nothing to pin. Every request reads the directory as it stands at that moment. There is no snapshot identifier, no as-of parameter and no version boundary in the cursor. Two of the four fields in the sort key are facts that move: whether a company is witnessed, and the time it was last witnessed. A key that moves across your position has two outcomes, and the response marks neither.
Both follow from paging a list whose sort key is the thing being updated, and a cursor on its own cannot close either. Treat one walk as one walk, not as one consistent view of the Network.
Key what you receive on slug and drop a repeat; that covers the duplicate, and it is cheap. The omission cannot be detected from inside a single walk, so for a set you intend to treat as complete, walk it again and reconcile the two results rather than trusting one walk. A short walk is worth more than a careful one here: the smaller the window between your first page and your last, the less the list moves underneath it. If your reconciler needs a boundary to work from, use last_scan on the rows themselves rather than the time you made the request.
Every key on a page sorts after the marker you sent, so the marker strictly advances and you cannot loop on the same one. That is not the same as a bounded number of pages: rows added ahead of your position are served to you, so a walk of a fast-growing list can run longer than you expect. Cap the pages, refuse to send a marker you have already sent, and stop on next_cursor being null rather than on an empty page.
The cursor does not expire. It carries a payload version and a position, and no issue time, so a marker kept from last week is still read and still honored against today's list. What is refused is a marker from an older payload version, a marker that cannot be decoded, and a marker issued to a workspace-scoped list being used on a public one. Each is a 400 naming which, not a silent reset to page one, because silently restarting is how a client pages for ever without noticing.
The cursor is not bound to your query. q and industry are not carried in it. Send the same cursor with a different filter and it is accepted: you get rows of the new filter that sort after the old position, which is almost certainly not what you meant and looks like a short page rather than an error. Keep the filter beside the cursor in your own code. total is included in every page and is the size of the filtered set on that request, so it changes between pages and is not a target to count up to.
The cursor is opaque and belongs to this list. Do not parse it, build one, or carry it to another endpoint; a marker from elsewhere is refused rather than silently misread.
offset and next_offset still work, because clients already shipped with them. They are deprecated: a response to a request that pages by offset carries a Deprecation header and a Link header with rel="deprecation" pointing here. No withdrawal date is set, so no Sunset header is sent; if one is ever set it will be announced in the changelog before it takes effect. The migration is one step rather than a rewrite: next_cursor is returned on the offset path too, so read it from whichever page you are on, send it as cursor, and stop sending offset. A request carrying both is answered from the cursor, since it is the one that is right.
# First pagecurl "https://trooth.co/api/mobile/directory/search?q=payments&limit=100"# Every next page: send next_cursor back as cursor,# and stop when next_cursor is nullcurl "https://trooth.co/api/mobile/directory/search?q=payments&limit=100&cursor=NEXT_CURSOR"There is none, and every public endpoint is a read. No operation in this reference creates or changes anything, so a repeated request is safe by construction and there is no idempotency key to send.
This is worth stating rather than leaving to inference, because the answer changes the day a write endpoint appears. When one does, it will carry an idempotency key and this section will say so. Until then, any client that retries a Trooth call is retrying a read.
GET /api/compareGET /api/legal/{slug}GET /api/mobile/directory/featuredGET /api/mobile/directory/searchGET /api/mobile/directory/vendor/{slug}GET /api/network/profileGET /api/network/suggestGET /api/public-mesh/{handle}GET /api/versionThe profile read is versioned by contract. The current contract is 2, and this deployment answers 1 and 2. Choose one with the Trooth-Contract request header or the contract query parameter. Send neither and you get the current contract.
400, not a guess at which you meant.400 with nothing approximated.Trooth-Contract response header, in contractVersion, and, for the one you asked for, in requestedContract.Version 1 is withdrawn on 2027-03-01. It promised one answer per question. That was not true where two sources disagree: the older resolver published whichever answer arrived first and dropped the other without saying so. This response is the version 2 shape, so `facts[].value` is one account and a row with `contested: true` has others in `conflicts`. Move to 2 and read `conflicts`. Until then, a request for version 1 is served the current contract with a Deprecation header and a Sunset header carrying the date.
The full policy is on API versioning, and every deprecation is announced in the changelog with its deadline before it takes effect.
# In a headercurl "https://trooth.co/api/network/profile?q=trooth.co" \ -H "Trooth-Contract: 2"# Or in the querycurl "https://trooth.co/api/network/profile?q=trooth.co&contract=2"{ "found": false, "error": "unsupported_contract_version", "message": "This deployment answers contract versions 1, 2 and was asked for 9. ...", "contractVersion": 2}There is one environment, production, and no test mode. Because every public endpoint is a read of published data, a call made while you build changes nothing and costs nothing, so the production endpoints are safe to develop against.
What production cannot do is put a company into a state on request. A domain under .invalid, which is reserved and can never be registered, reliably returns found: false, and a missing q reliably returns 400. For the rest, a failed read, a company with no observation and a contested fact, write fixtures from the published schema and test your handling against them. Those are the states integrations most often get wrong, and they are the ones you will not meet by chance while building.
Validate what you receive against the schema in your own tests. It is the document the route is tested against on every push and checked against after every deployment, so a response that fails it is worth reporting.
# The schema the route is tested against on every pushcurl -O "https://trooth.co/schemas/network-profile.v2.schema.json"| State | How to reach it |
|---|---|
| found: true | q=trooth.co |
| found: false | q=nothing-here.invalid |
| 400 | no q, or contract=9 |
| 503 | A fixture. It cannot be requested. |
| standing: null | A fixture built from the schema. |
| A contested fact | A fixture built from the schema. |
Four stable objects underpin every Trooth surface. They are versioned conservatively: fields are added, never silently changed.
# Public read. No key, no account.curl "https://trooth.co/api/network/profile?q=your-co.com"# The published record. Abridged: the response also carries contractNote,# contractOmissions, deprecation and methodology, and the full field list is# https://trooth.co/schemas/network-profile.v2.schema.json{ "found": true, "contractVersion": 2, "contractSchema": "https://trooth.co/schemas/network-profile.v2.schema.json", "slug": "your-co", "name": "Your Co", "domain": "your-co.com", "canonicalUrl": "https://trooth.co/network/company/your-co", "tagline": "Payments infrastructure for marketplaces", "industries": ["Payments"], "updatedAt": "2026-08-22T14:03:11Z", "witnessed": { "standing": "witnessed", "lastWitnessed": "2026-08-22T14:03:11Z", "firstWitnessedAt": "2026-06-01T09:12:40Z", "coverage": { "checksPassed": 11, "checksRun": 12 }, "continuity": { "recordBegan": "2026-06-01T09:12:40Z", "unbrokenSince": "2026-07-04T02:00:00Z", "unbrokenReadings": 1176, "totalReadings": 1904 } }, "facts": [ { "key": "hosting.production-regions", "category": "Hosting", "label": "Production regions", "value": "us-east-1, eu-west-1", "origin": "company-declared" } ], "conflicts": []}{ "key": "transport.tls-version", "category": "Transport", "label": "TLS version", "value": "TLS 1.3", "origin": "witnessed", "contested": false}{ "receipt_id": "rcpt_01J8Z9M4T6Q2", "subject": "your-co.com", "source": "trooth-witness-scan", "scope": "transport, dns, disclosure, supply-chain, app-sec", "observed_at": "2026-08-22T14:03:11Z", "freshness": { "category": "security", "stale_at": "2026-08-24T14:03:11Z" }, "signature": { "alg": "ed25519", "key_id": "trooth-2026-08" }}{ "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 }}Every material fact carries where it came from, and a client that ignores that will report things Trooth did not say. Three rules matter more than the rest.
The full vocabulary and how each state is reached is in the methodology.
This is a single entry from facts[] on GET /api/network/profile, which is the unit a Trust Profile is built out of:
{ "key": "hosting.production-regions", "category": "Hosting", "label": "Production regions", "value": "us-east-1, eu-west-1", "origin": "company-declared" }key is stable and scoped to its category, which is not tidiness: Region under hosting and Region under registration are different questions, and a comparison aligned on the bare label puts one company's data residency beside another's registered address. origin is one of three values, company-declared, witnessed and public-source, and it is most of what the fact is worth. A row whose value is blank is not published at all, so an unanswered question reads as unanswered rather than as an empty string.
Where two sources answered one question differently, the fact carries contested and every account sits under the same key in the top-level conflicts array:
"conflicts": [ { "key": "hosting.production-regions", "category": "Hosting", "label": "Production regions", "accounts": [ { "origin": "company-declared", "value": "eu-west-1" }, { "origin": "witnessed", "value": "us-east-1, eu-west-1" } ] } ]On a contested fact, value is one account and not the answer. There is no winner field, no preferred source, and the order accounts appear in is the origin vocabulary's fixed order rather than a ranking. Deciding between two accounts is a judgment about the company, and Trooth does not make it for you.
These get collapsed into one another more often than anything else on this contract, and each collapse produces a different wrong sentence about a real company.
facts[].origin, one per fact, always present. Trooth reading a live system and a company writing an answer are different claims.found: false means the Network holds no such record, a 503 means the read failed and asserts nothing about the company, and witnessed.standing: null means no observation came back on the last reading. Three different kinds of nothing. Collapsing them is how an outage becomes a published finding.witnessed.lastWitnessed for the last reading and methodology.freshness for the window each category is expected to be refreshed within. It is not on the fact. Do not manufacture a per-fact date by copying the record's.contested and conflicts, shown above. An uncontested fact is one nobody has answered differently, which is not the same as one that has been corroborated.200 from this endpoint is not a signature over its contents. When the reading behind witnessed was signed under Trooth's witness statement, the body carries witnessStatement: its payload is the exact string Trooth signed, the facts of that reading with no verdict, and its signature is Ed25519 over those bytes, checked with the key its key_id names at verify/keys. An older reading has no statement and the field is absent. Every other field is unsigned.The contract also names what it deliberately does not carry, in contractOmissions on every response: there is no confidence number per fact or overall, no adjudication between disagreeing sources, no completeness signal, and no merging of two wordings that look equivalent. Each of those is something a reader will otherwise infer from the fields that are present.
Every response carries contractVersion. It is 2 today and it moves only when a field is removed or renamed or when the meaning of a value changes, so adding a field does not move it and a caller reading known fields is safe across additions. The versions still answered are 1 and 2. Pin one, and treat a value you did not expect as a reason to stop rather than to guess.
Version 1 is withdrawn on 2027-03-01. It promised one answer per question. That was not true where two sources disagree: the older resolver published whichever answer arrived first and dropped the other without saying so. This response is the version 2 shape, so `facts[].value` is one account and a row with `contested: true` has others in `conflicts`. Move to 2 and read `conflicts`. That notice also arrives in the deprecation field of every response served at version 1, while it still works, so a caller does not have to be reading this page to find out.
| Dimension | Read it from |
|---|---|
| Provenance | facts[].origin |
| Availability | found, a 503, witnessed.standing |
| Freshness | witnessed.lastWitnessed, methodology.freshness |
| Dispute | facts[].contested, conflicts |
| Integrity | witnessStatement |
Nine operations, every one a read of published data, and none of them needs a credential. The machine-readable contract is at /openapi.json, OpenAPI 3.1, generated from the source and checked on every build.
GET /api/compareCompare companies side by sideGET /api/legal/{slug}A published legal documentGET /api/mobile/directory/featuredCompanies with a witnessed recordGET /api/mobile/directory/searchSearch the Trooth NetworkGET /api/mobile/directory/vendor/{slug}One company recordGET /api/network/profileOne company's canonical profile and witness factsGET /api/network/suggestTypeahead over published companiesGET /api/public-mesh/{handle}A company's public evidence rollupGET /api/versionThe deployment currently servingEach operation has its own entry below, with its parameters, the answers it gives and a request you can copy.
https://trooth.coGET /api/compareGET /api/legal/{slug}GET /api/mobile/directory/featuredGET /api/mobile/directory/searchGET /api/mobile/directory/vendor/{slug}GET /api/network/profileGET /api/network/suggestGET /api/public-mesh/{handle}GET /api/versionGET /api/network/profile
One company's canonical profile and witness facts.
The canonical published profile for a company, keyed by domain or slug, with the witness facts a buyer's tool needs: the standing field (the witnessed state), lastWitnessed (freshness), firstWitnessedAt and the unbroken run of Trooth's own hourly readings (continuity), and checksPassed out of checksRun on the signed result (coverage: checks that read as expected, out of checks run). Continuity is never claimed older than Trooth's own record and stays null until two consecutive readings agree. Responds { found: false } with 200 when no profile is published, 400 when q is missing. Cached at the edge for 120 seconds. Every response carries a methodology object stating the reading cadence, the gap tolerance that defines an unbroken series, the retention period, the per-category freshness windows, and an explicit list of coverage limits - what Trooth does not read. A caller restating witnessed or updatedAt elsewhere must carry those limits with it: neither field is a certification nor an audit opinion.
curl "https://trooth.co/api/network/profile?q=example.com"{ "found": true, "contractVersion": 2, "contractNote": "string", "contractOmissions": [ "string" ], "deprecation": "string", "contractSchema": "https://trooth.co/schemas/network-profile.v2.schema.json", "slug": "example-co", "name": "string", "domain": "string", "canonicalUrl": "https://trooth.co/network/company/example-co", "tagline": "string", "industries": [ "string" ], "updatedAt": "string", "witnessed": { "standing": "witnessed", "lastWitnessed": "string", "firstWitnessedAt": "string", "coverage": { "checksPassed": 0, "checksRun": 1 }, "continuity": { "recordBegan": "string", "unbrokenSince": "string", "unbrokenReadings": 0, "totalReadings": 0 } }, "witnessStatement": { "payload": "string", "signature": "ed25519:BASE64SIGNATURE==", "key_id": "string", "alg": "Ed25519", "canonicalization": "string" }, "methodology": { "cadence": "string", "gapTolerance": "string", "retention": "string", "freshness": [ { "category": "string", "label": "string", "window": "string" } ], "exclusions": [ "string" ], "summary": "string" }, "facts": [ { "key": "string", "category": "string", "label": "string", "value": "string", "origin": "company-declared" } ], "conflicts": [ { "key": "string", "category": "string", "label": "string", "accounts": [ { "origin": "see the schema", "value": "see the schema" } ] } ], "signing": { "profileSigned": false, "signedArtifact": "witnessStatement", "subject": "string" }, "requestedContract": 0, "evidenceClasses": {}, "authority": {}, "relationships": {}}GET /api/mobile/directory/search
Search the Trooth Network.
Full-text search over published company listings. Returns the same companies the public directory shows. Prefer cursor pagination: pass the returned next_cursor back as cursor. Offset pagination still works and is kept for clients that already ship with it, but it is deprecated: it counts rows rather than naming a position, so a directory that grows between two requests can serve the same company twice and skip another without erroring. A response to an offset-paged request carries a Deprecation header and a Link header with rel="deprecation"; no withdrawal date is set, so no Sunset header is sent. A cursor that cannot be read returns 400 rather than an empty page. Cached at the edge for 60 seconds with a 300 second stale-while-revalidate window.
curl "https://trooth.co/api/mobile/directory/search"{ "results": [ { "slug": "string", "name": "string", "standing": "Witnessed" } ], "total": 0, "next_offset": 0, "next_cursor": "string", "error": "string"}GET /api/mobile/directory/featured
Companies with a witnessed record.
The Trooth Network's front page: published companies, those with a witnessed record first. Cached at the edge for 120 seconds.
None.
curl "https://trooth.co/api/mobile/directory/featured"{ "results": [ { "slug": "string", "name": "string", "standing": "Witnessed" } ]}GET /api/mobile/directory/vendor/{slug}
One company record.
A published company profile, and its live witnessed state where Trooth has witnessed the company. A slug of the form d--example.com resolves a witnessed company that has not yet published a rich profile. Cached at the edge for 120 seconds.
curl "https://trooth.co/api/mobile/directory/vendor/example-co"{ "slug": "string", "name": "string", "standing": "Witnessed", "tagline": "string", "last_scan": "string"}GET /api/network/suggest
Typeahead over published companies.
Name-to-company suggestions for a search box, over the same published listings the directory shows. Cached at the edge for 60 seconds.
curl "https://trooth.co/api/network/suggest?q=example.com"{ "results": [ { "name": "string", "domain": "string", "slug": "string", "witnessed": true } ]}GET /api/public-mesh/{handle}
A company's public evidence rollup.
The buyer-facing projection of a company's evidence: the witnessed state per pillar, provider status with the time each connected system was last read. Framework rollups were retired on 2026-09-26 and the frameworks array is always empty. Individual control rows, owners and internal notes are never returned here. Responds 404 when the company has no public record, which is a privacy-preserving answer rather than an error. Readable cross-origin (Access-Control-Allow-Origin: *); this is the read the embeddable badge performs. Cached at the edge for 300 seconds.
curl "https://trooth.co/api/public-mesh/example-co"{ "sample": true, "company": { "name": "string", "handle": "string", "updatedAt": "string" }, "frameworks": [], "pillars": [ { "id": "string", "label": "string", "status": "string" } ], "providers": [ { "id": "string", "label": "string", "status": "string" } ], "controls": [], "freshness30d": 0, "disclaimer": "string", "retired": {}}GET /api/compare
Compare companies side by side.
A comparison of published witnessed facts across up to a handful of companies. Control rows are stripped, so a company's internal posture is never exposed through the comparison. Rate limited.
curl "https://trooth.co/api/compare?handles=example-co%2Canother-co"{ "vendors": [ { "company": { "name": "string", "handle": "string", "updatedAt": "string" }, "frameworks": [], "pillars": [ { "id": "see the schema", "label": "see the schema", "status": "see the schema" } ], "providers": [ { "id": "see the schema", "label": "see the schema", "status": "see the schema" } ], "controls": [] } ], "unavailable": [ "string" ], "frameworkRows": [], "retired": {}}GET /api/legal/{slug}
A published legal document.
The full text of one of the twenty published Trooth legal documents, with its title, effective date and body. The slug must be one of the published set; anything else responds 404. Cached at the edge for one hour with a one day stale-while-revalidate window.
curl "https://trooth.co/api/legal/example-co"{ "slug": "string", "title": "string", "effective": "string", "html": "string"}GET /api/version
The deployment currently serving.
The build stamp of the running deployment. Never cached. Useful for confirming which release answered a request.
None.
curl "https://trooth.co/api/version"{ "stamp": "string"}Retrieval agents are welcome and robots.txt says so explicitly: an assistant answering a question about a vendor right now is the case this network exists for. Model-training crawlers are blocked, because the companies whose evidence is published here did not consent to wholesale ingestion.
Cite what you read. Every material fact has a source and a date, and an answer that carries them is worth more than one that does not. When Trooth holds no record, say so rather than filling the gap.
The Model Context Protocol (MCP) server is live at https://api.trooth.co/public/mcp, over JSON-RPC 2.0. It is public and needs no key. It leads with protocol 2026-07-28 and reports itself as trooth-mcp 1.1.0, and it still answers 2025-06-18, 2025-03-26 or 2024-11-05, so a client written against the earlier handshake keeps working. It serves 4 tools: trooth_public_trust_profile, trooth_outside_in_read, trooth_verify and trooth_ask. Each is read only, takes one required string argument, and answers with structuredContent (status, provenance, subject, summary) next to the text. The server also lists 4 resources (trooth://methodology, trooth://provenance-labels, trooth://verify-a-vendor, trooth://what-a-call-sends) and 3 prompts (vendor_trust_check, verify_trust_token, before_you_trust). Setup and the full catalog are on Agents and MCP, and changes are announced in the changelog.
https://api.trooth.co/public/mcp{ "mcpServers": { "trooth": { "type": "http", "url": "https://api.trooth.co/public/mcp" } }}Companies configure webhooks in their own workspace to push posture changes into their tools. There is also one public subscription route for agents and buyers: POST https://api.trooth.co/public/trust/:slug/agent-subscribe with a webhookUrl returns a signing secret, follows published profiles only, and delivers trust.posture.changed events signed with x-trooth-signature. That event and its envelope belong to this route alone; the workspace endpoints have their own event list. Details are on Agents and MCP.
There is no SDK, and no Trooth package under a scope: any @trooth/ scope name you find is not ours. The endpoints are plain HTTP and JSON, and a fetch or a curl is the whole integration. A generated client from /openapi.json works if you want types.
One package is published: trooth on npm, the command line client, source at github.com/troothllc/trooth-cli. It has two commands. trooth check <domain> reads a public record, and trooth lint [path] reads what your own repository declares and makes no request at all. It is not a wrapper around this reference: it does not read a key and it reaches only the directory endpoint. Anything else you have seen it described as doing, trooth scan in particular, was retired and now exits with a sentence explaining that rather than running.
POST https://api.trooth.co/public/trust/example.com/agent-subscribeContent-Type: application/json{ "webhookUrl": "https://your-service.example/hooks/trooth" }npx trooth check example.comnpx trooth lint .Six checks, each one a failure an integration has already had somewhere.
The terms are the service level agreement and the API terms.
| What | Where |
|---|---|
| Live availability | /status |
| Incident history, as a feed | /status/history.rss |
| API changes, as a feed | /changelog/rss.xml?area=api |
| Security changes, as a feed | /changelog/rss.xml?type=security |
| Versioning policy | /api-versioning |
| A person | developers@trooth.co |
Breaking changes to the shape of these responses are announced in the changelog before they ship, and the versioning and deprecation policy is at API versioning. Live availability is at status, and GET /api/version tells you which deployment answered your request.
Questions go to developers@trooth.co.
curl "https://trooth.co/api/version"# { "stamp": "..." }Something on this page wrong, unclear or missing? Write to developers@trooth.co. A correction to this reference is recorded in the changelog.
A security reviewer and an AI procurement agent read the same record, with its sources, its dates and the signature behind each reading.