Reference · Contract 2 · Public reads · No key
Every public fact on Trooth is structured and source-labeled. Witnessed facts carry a read time and signature; an unrecorded date reads as unknown, never borrowed.
The Trooth application programming interface (API) answers questions about software and AI companies from published records. Every endpoint in this reference is a read over an encrypted connection, in JavaScript Object Notation (JSON), with no key and no account. Public calls that change state, such as subscribing an endpoint to a company's changes, are named in the interface catalog with their side effects.
Each answer carries its own limits:
No response contains a rating, grade or rank, and the schema refuses one.
There is no write endpoint, test environment, software development kit (SDK) or credentialed public API; alternatives are given below.
| 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 | Older read for existing clients; Network companies answer moved, naming the supported lookup. Contract: /openapi.yaml |
# Nothing to install.curl --versionnpm install -g troothReading the Network needs no account. Writing into your own workspace needs a key bound to your client. They have different addresses, credentials and contracts; every operation below says which track it is on.
| Aspect | Public Network API | Workspace API |
|---|---|---|
| For | Anyone reading a company's public record: buyers, tools and AI agents. | A company's own workspace, sending evidence events into it. |
| Base address | https://trooth.co; the canonical read is GET /api/network/profile. The MCP server is https://api.trooth.co/public/mcp, also keyless. | https://api.trooth.co |
| Credential | None: no key, no account, no cookie. | A workspace API key (tk_live_) bound to your client's key with DPoP (RFC 9449), sent with a new proof on every request. A key created without that binding is an ingest key that reaches only POST /v1/evidence/webhook. |
| Scope | Published records only; nothing private is reachable. | One workspace: a key acts only for the workspace whose member created it. |
| Lifecycle | Pin ?contract=2; a breaking change moves the number, with 90 days' notice under the versioning policy. | A key is created by a member of the workspace, shown once and stored only as a hash; revocation takes effect on the next request. |
| Limits | Per client network, stated on each operation; the MCP server meters units per caller. | Not published per route today; the authorization model says what each credential can reach. |
| Contract | openapi.json and the contract 2 schema. | openapi.yaml and the authorization model. |
| Start here | Quickstart: one GET, no setup. | API keys: create a bound key in your workspace. |
Listed means live now. Anything not listed is not live; Trooth does not advertise unshipped endpoints.
npx trooth check trooth.coYour first working call, in about five minutes
Any client that speaks an encrypted connection; the samples need Node.js 18+ or Python 3.8+. No account, no key.
Send GET /api/network/profile with a domain in q; q=trooth.co always has an answer.
An unknown company is 200 with { "found": false }, an answer. A 503 means Trooth could not read its own record and says nothing about the company.
Every entry in facts carries origin: company-declared, Trooth-witnessed or public. Show it beside the value.
Send Trooth-Contract: 2 so no change of meaning arrives unannounced. The response names the contract that served it in the same header.
That works. Next, page with the cursor, follow changes, and 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 reads published records.
Trooth's authenticated endpoints run the workspaces, are session scoped and are deliberately not public: a documented route map helps attackers and promises unsupported integrations. A credentialed API, if built, 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; the status code carries the meaning:
400: the request was malformed. The body names the field.404: the Network holds no such record. An answer, not a failure.429: a rate limit. Honor Retry-After when present; otherwise pause and retry once.502 and 503: Trooth could not read its upstream, which says nothing about the company. Retry with a bound.5xx: ours. Retry with the same bound.Every API response has an X-Request-Id: send your own (20 to 200 letters, digits, + / = -) or get one. Quote it when reporting failures; a W3C traceparent you send is continued.
Send Accept: application/problem+json to /api/network/profile or /api/network/profile/history for RFC 9457 problem documents; otherwise the body is unchanged.
The document carries:
code, stable, and type, which links to that code below.retryable, with retry_after and retry_at when a wait is known.next_action.errors, naming the parameter or field.request_id, the same value as the header.invalid_request 400 The request could not be read as sent. Next: fix_request.missing_parameter 400 A required parameter is missing. Next: fix_request.unsupported_contract_version 400 That contract version is not served. Next: fix_request.invalid_cursor 400 That page marker cannot be used. Next: start_over.cursor_expired 400 That page marker has expired. Next: start_over.not_found 404 No published record answers to that identifier. Next: none.version_not_found 404 That record version is not published. Next: none.beyond_retention 410 That position is older than the change history keeps. Next: resync.rate_limited 429 Too many requests. Next: retry_later.busy 429 A request like this one is still running. Next: retry_later.capacity_saturated 503 Trooth is running as many of these as it allows at once. Next: retry_later.Three rules. The third is the one most often left out.
429, 502, 503 and other 5xx, never 400 or 404. On a refused cursor, restart from page one, once.503 does not mean no record; keep the last good answer or show the failed read.| 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.");}Per IP address, per minute and per hour; over either, 429 with a plain JSON message.
Only comparison is rate limited by this site: 20 requests a minute and 200 an hour per address.
Read the header before waiting. Whether Retry-After is sent depends on the route, not the status:
GET /api/public-mesh/{handle} sends one with its 503.429.429 from comparison does not.If present, wait that long; if absent, back off and cap attempts (see Errors).
Comparison fans out, so it is the tightest; other reads are edge-cached:
| Interface | Call | Access | Side effects | Availability | Support |
|---|---|---|---|---|---|
| Search the directory | GET trooth.co/api/mobile/directory/search | Public read | None: a read | Available | supported |
| Featured companies | GET trooth.co/api/mobile/directory/featured | Public read | None: a read | Available | supported |
| One directory entry | GET trooth.co/api/mobile/directory/vendor/{slug} | Public read | None: a read | Available | supported |
| A company's public rollup | GET trooth.co/api/public-mesh/{handle} | Public read | None: a read | Available | supported |
| A company's Trust Profile (the canonical lookup) | GET trooth.co/api/network/profile | Public read | None: a read | Available | supported |
| A company record's published versions: the change feed, and a read of one named version | GET trooth.co/api/network/profile/history | Public read | None: a read | Available | documented, not versioned |
| The section map: which sections each machine projection carries, leaves out, or cannot carry yet | GET trooth.co/section-capabilities.json | Public read | None: a read | Available | documented, not versioned |
| Compare companies | GET trooth.co/api/compare | Public read | None: a read | Available | supported |
| Name suggestions | GET trooth.co/api/network/suggest | Public read | None: a read | Available | supported |
| Multi-criteria discovery | GET trooth.co/api/network/search | Public read | None: a read | Available | supported |
| Batch read | POST trooth.co/api/network/batch | Public read | None: a read | Available | supported |
| Network change feed | GET trooth.co/api/network/changes | Public read | None: a read | Available | supported |
| Network change stream (Server-Sent Events) | GET trooth.co/api/network/changes/stream | Public read | None: a read | Available | supported |
| Error code registry | GET trooth.co/api/problems | Public read | None: a read | Available | supported |
| A legal document | GET trooth.co/api/legal/{slug} | Public read | None: a read | Available | supported |
| The deployed version | GET trooth.co/api/version | Public read | None: a read | Available | supported |
| A Trust Profile by workspace slug (older worker route) | GET api.trooth.co/public/trust/{slug} | Public read | None: a read | Available | deprecated, with a sunset date |
| Public signing keys | GET api.trooth.co/public/keys | Public read | None: a read | Available | supported |
| A company's badge image | GET api.trooth.co/public/trust/{slug}/badge.svg | Public read | None: a read | Available | supported |
| The embeddable badge script | GET trooth.co/badge.js | Public read | None: a read | Available | supported |
| Record a profile view | POST api.trooth.co/public/trust/{slug}/view | Public, changes state | records a view | Available | documented, not versioned |
| Subscribe an endpoint to a company's changes | POST api.trooth.co/public/trust/{slug}/agent-subscribe | Public, changes state | stores a subscription and sends requests later | Available | supported |
| Subscribe an address to a company's changes | POST api.trooth.co/public/trust/{slug}/subscribe | Public, changes state | stores a subscription and sends requests later | Available | documented, not versioned |
| Ask a company for a document | POST api.trooth.co/public/trust/{slug}/request-access | Public, changes state | stores a request | Available | documented, not versioned |
| MCP tool trooth_public_trust_profile: a company's complete Trust Profile, section by section, with a short summary and a link to the full record | POST api.trooth.co/public/mcp trooth_public_trust_profile | Public read | None: a read | Available | supported |
| MCP tool trooth_ai_data_use_disclosures: a company's published AI and data-use declarations | POST api.trooth.co/public/mcp trooth_ai_data_use_disclosures | Public read | None: a read | Available | supported |
| MCP tool trooth_search_companies: search the companies published on the Trooth Network | POST api.trooth.co/public/mcp trooth_search_companies | Public read | None: a read | Available | supported |
| MCP tool trooth_compare_companies: two to four companies' Trust Profiles side by side, section by section | POST api.trooth.co/public/mcp trooth_compare_companies | Public read | None: a read | Available | supported |
| MCP tool trooth_outside_in_read: a live read of a domain's public surface | POST api.trooth.co/public/mcp trooth_outside_in_read | Public read | requests the named site once | Available | supported |
| MCP tool trooth_verify: check the signatures on a Trust Ledger Token | POST api.trooth.co/public/mcp trooth_verify | Public read | None: a read | Available | supported |
| MCP tool trooth_ask: a question about Trooth itself | POST api.trooth.co/public/mcp trooth_ask | Public read | None: a read | Available | supported |
| MCP tool trooth_my_company_record: your own workspace's company record | POST api.trooth.co/public/mcp trooth_my_company_record | Private read, sign-in | None: a read | Available | documented, not versioned |
| The A2A Agent Card: this agent's interface, not any company's record | GET api.trooth.co/.well-known/agent-card.json | Public read | None: a read | Available | supported |
| The A2A agent: three read-only tasks over A2A 1.0, JSON-RPC binding | POST api.trooth.co/a2a/v1 SendMessage: company_record, verify_token, ask | Public read | None: a read | Available | supported |
| A2A task retention: GetTask, ListTasks and CancelTask on a task only your token can read back | POST api.trooth.co/a2a/v1 GetTask, ListTasks, CancelTask | Private read, sign-in | None: a read | Available | documented, not versioned |
| Browser tools on every public page: search the Network, read a Trust Profile, read this page | WebMCP trooth.co trooth_search_network, trooth_read_trust_profile, trooth_read_this_page | Public read | None: a read | Available | documented, not versioned |
| Browser tools in a signed-in workspace: read, fill, save and publish the company's own profile | WebMCP trooth.co trooth_profile_read, trooth_profile_field_catalog, trooth_profile_update_fields, trooth_profile_save_draft, trooth_profile_publish | Browser publication, signed in | saves or publishes the company's profile | Available | documented, not versioned |
| The trooth command line | npx trooth check, lint, verify, log, mirror, public-record, mcp-tools, guard, declare | Public read | None: a read | Available | supported |
| Any public page as Markdown | GET trooth.co/{page}.md, or Accept: text/markdown | Public read | None: a read | Available | documented, not versioned |
| Surface | Limit | Over it |
|---|---|---|
GET /api/compare | 20 a minute and 200 an hour, per IP address | 429, no Retry-After |
GET /api/network/profile | 600 a minute and 20,000 an hour per client network (an IPv4 address or an IPv6 /64), shared across instances | 429 with Retry-After: the seconds until that window resets |
GET /api/network/profile/history | 120 a minute and 3,000 an hour per client network (an IPv4 address or an IPv6 /64), shared across instances | 429 with Retry-After: the seconds until that window resets |
| 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 | 300 units every 60 seconds per caller (OAuth subject, otherwise IP address), shared across every server instance; a tool call costs 1 to 4 | 429 with Retry-After giving the seconds until units free up |
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 each returned next_cursor as cursor until next_cursor is null. An unreadable cursor answers 400, not an empty page.
The cursor carries the sort key, four fields in order:
The identifier breaks ties between companies that share a name.
So every row has one position. The key is in lib/directory-rank.ts; the comparator derives from it.
It prevents the offset failure. With offsets, companies listed mid-walk shift rows, so you silently get some twice and miss others.
A cursor names a position, so inserted rows do not shift it.
It is not a snapshot, and there is nothing to pin. Each request reads the live directory; there is no snapshot id, as-of parameter or version boundary.
Two sort-key fields move (whether and when a company was witnessed), with two unmarked outcomes:
No cursor prevents either; a walk is best-effort discovery over a list that changes, not a consistent view of the Network.
Drop repeats by slug. The omission is undetectable within one walk:
last_scan, not your request time.The marker always advances, so a walk cannot loop, but pages are unbounded: rows added ahead of you are served too.
Cap the pages, never resend a marker, and stop on a null next_cursor, not an empty page.
The cursor does not expire. It holds a payload version and a position, no issue time, so old markers still work.
These get a 400 naming the reason, never a silent reset to page one:
The cursor is not bound to your query. q and industry are not in it, so another filter returns that filter's rows after the old position, like a short page.
Keep the filter beside the cursor. total is per request and changes between pages; do not count up to it.
The cursor is opaque and belongs to this list; do not parse, build or reuse it elsewhere, where it is refused.
offset and next_offset still work for clients that shipped with them. They are deprecated:
offset carry Deprecation and Link (rel="deprecation") headers pointing here.Sunset header is sent; the changelog would announce one first.Migrating takes one step. Send the offset path's next_cursor as cursor and drop offset; with both, the cursor wins.
# 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"Every public endpoint documented here is a read, POST /api/network/batch included, so a repeat is always safe and needs no key.
Writes take one. Signed-in writes on trooth.co (publishing a profile, a domain claim, an invitation) accept an Idempotency-Key header of 8 to 255 characters, kept for 24 hours: the same key with the same request returns the stored answer, the same key with a different request is refused with 409, and the Idempotency-Status response header says which happened. Writes to api.trooth.co follow its own rule, set out under Authorization in the API documentation, where a reused key with a different request is 422.
GET /api/compareGET /api/legal/{slug}GET /api/mobile/directory/featuredGET /api/mobile/directory/searchGET /api/mobile/directory/vendor/{slug}GET /api/network/changesGET /api/network/changes/streamGET /api/network/profileGET /api/network/searchGET /api/network/suggestGET /api/problemsGET /api/public-mesh/{handle}GET /api/versionPOST /api/network/batchThe profile read is versioned: 2 is current; 1 and 2 are answered. Pick one with the Trooth-Contract header or contract parameter; send neither for the current one.
400.400; nothing is approximated.Trooth-Contract header and contractVersion, and the one you asked for in requestedContract.Version 1 is withdrawn on 2027-03-01. It promises one answer per question, which holds only where no two sources disagree, so this version 1 response leaves out every fact two sources answered differently and names each in `withheldContested`; version 2 publishes them with every account in `conflicts`. Move to 2 and read `conflicts`. Until then, version 1 is still served, with Deprecation and dated Sunset headers.
Policy: API versioning. Each deprecation and deadline is announced first in the changelog.
# 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. Reads change nothing and cost nothing.
Production cannot put a company into a state on request. Two states are reliable:
.invalid domain, which can never be registered, returns found: false.q returns 400.For a failed read, a company with no observation and a contested fact, write fixtures from the schema; you will not meet these by chance.
Validate responses against the schema. The route is tested against it every push and deployment, so a failure 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. 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 its source. Three rules:
The full vocabulary and how each state is reached is in the methodology.
One entry from facts[] on GET /api/network/profile, a Trust Profile's unit:
{ "key": "hosting.production-regions", "category": "Hosting", "label": "Production regions", "value": "us-east-1, eu-west-1", "origin": "company-declared" }key is stable and category-scoped: Region under hosting and Region under registration are different questions.
origin is company-declared, witnessed or public-source. Blank values are not published, so unanswered reads as unanswered.
Disagreeing sources mark a fact contested, with every account under its key in top-level conflicts:
"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, not the answer. There is no winner field or preferred source; accounts follow a fixed origin order, not a ranking.
Collapsing any two produces a wrong sentence about a real company.
facts[].origin, always present: a witnessed reading or a company's answer.found: false (no record), a 503 (read failed, asserts nothing) and witnessed.standing: null (no observation on the last reading). Collapse them and an outage becomes a finding.witnessed.lastWitnessed plus each category's methodology.freshness window, never a per-fact date.contested and conflicts, shown above. Uncontested means nobody answered differently, not corroborated.200 are unsigned. If the reading behind witnessed was signed, witnessStatement holds the signed string in payload (facts, no verdict) and an Ed25519 signature, checked with the key_id key at verify/keys.Older readings have no statement; all other fields are unsigned.
Each response lists in contractOmissions what the contract deliberately leaves out:
Every response carries contractVersion (2 today). It moves only for a removed or renamed field or a changed meaning, never an added field.
Still answered: 1 and 2. Pin one; stop on unexpected values.
Version 1 is withdrawn on 2027-03-01. It promises one answer per question, which holds only where no two sources disagree, so this version 1 response leaves out every fact two sources answered differently and names each in `withheldContested`; version 2 publishes them with every account in `conflicts`. Move to 2 and read `conflicts`. Until then, each version 1 response repeats this in deprecation.
| Dimension | Read it from |
|---|---|
| Provenance | facts[].origin |
| Availability | found, a 503, witnessed.standing |
| Freshness | witnessed.lastWitnessed, methodology.freshness |
| Dispute | facts[].contested, conflicts |
| Integrity | witnessStatement |
Fourteen operations, all credential-free reads of published data. The contract, /openapi.json (OpenAPI 3.1), is generated from source and checked 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 recordPOST /api/network/batchCheck many companies in one callGET /api/network/changesEvery record version across the NetworkGET /api/network/changes/streamEvery record version, as Server-Sent EventsGET /api/network/profileOne company's canonical profile and witness factsGET /api/network/searchDiscover companies by several criteriaGET /api/network/suggestTypeahead over published companiesGET /api/problemsEvery error code and whether retrying can helpGET /api/public-mesh/{handle}A company's public evidence rollupGET /api/versionThe deployment currently servingEach operation below lists its parameters, its answers 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/changesGET /api/network/changes/streamGET /api/network/profileGET /api/network/searchGET /api/network/suggestGET /api/problemsGET /api/public-mesh/{handle}GET /api/versionPOST /api/network/batchGET /api/network/profile
One company's canonical profile and witness facts.
Public Network API, no key · the canonical company read
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. Only the witness statement is signed; the profile, its facts and the company's text are not (see signing). sectionCapabilities links the section map, which says which sections this record and the other projections carry. Limited to 600 requests a minute and 20,000 an hour per client network (an IPv4 address, or an IPv6 /64), counted across server instances; over either window the answer is 429 with Retry-After set to the seconds until that window resets. If the counting store fails, each instance counts alone at the same ceilings and the read is not refused. Readable cross-origin: the response carries Access-Control-Allow-Origin: * and never Access-Control-Allow-Credentials, so a browser on any site may read it without cookies.
curl "https://trooth.co/api/network/profile?q=example.com"{ "contractVersion": 2, "contractNote": "string", "contractOmissions": [ "string" ], "deprecation": "string", "requestedContract": 0, "contractSchema": null, "witnessStatement": { "payload": "string", "signature": "ed25519:BASE64SIGNATURE==", "key_id": "string", "alg": "Ed25519", "canonicalization": "string" }, "witnessEvidenceManifest": [ { "check_id": "string", "source": "string" } ], "signing": { "profileSigned": false, "signedArtifact": "witnessStatement", "subject": "string" }, "assuranceBoundary": { "statement": "This record organizes source-linked information for a security review. It is not an audit, certification, or suitability decision.", "doesNotEstablish": [ "string" ], "methodology": "string" }, "found": true, "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 } }, "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" } ], "withheldContested": [ "string" ]}GET /api/mobile/directory/search
Search the Trooth Network.
Public Network API, no key · supported
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. BEST-EFFORT, MUTABLE ENUMERATION: there is no snapshot. Every page reads the live directory, and the cursor names a position in the sort order, not a point in time; it is not bound to a snapshot, an as-of time or an expiry. A company whose witnessed state changes between two pages can be missed or returned twice, so a walk is not a consistent full export. Drop repeats by slug and walk again to reconcile. The fact pages of /api/network/profile are a different mechanism: their cursor is bound to one record version. Readable cross-origin: the response carries Access-Control-Allow-Origin: * and never Access-Control-Allow-Credentials, so a browser on any site may read it without cookies.
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.
Public Network API, no key · supported
The Trooth Network's front page: published companies, those with a witnessed record first. Cached at the edge for 120 seconds. Readable cross-origin: the response carries Access-Control-Allow-Origin: * and never Access-Control-Allow-Credentials, so a browser on any site may read it without cookies.
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.
Public Network API, no key · supported
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. Readable cross-origin: the response carries Access-Control-Allow-Origin: * and never Access-Control-Allow-Credentials, so a browser on any site may read it without cookies.
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.
Public Network API, no key · supported
Name-to-company suggestions for a search box, over the same published listings the directory shows. Cached at the edge for 60 seconds. Readable cross-origin: the response carries Access-Control-Allow-Origin: * and never Access-Control-Allow-Credentials, so a browser on any site may read it without cookies.
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.
Public Network API, no key · supported
The buyer-facing projection of a company's evidence: the witnessed state per area (the pillars key), 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. Each pillar carries observedAt and sourceRef, null when Trooth holds no per-area read time or reference, or when the area is restricted: null reads as unknown and is never filled from another area, a provider sync or the company's last edit. Responds 404 when the company has no public record, which is a privacy-preserving answer rather than an error. This is the read the embeddable badge performs. Cached at the edge for 300 seconds. Readable cross-origin: the response carries Access-Control-Allow-Origin: * and never Access-Control-Allow-Credentials, so a browser on any site may read it without cookies.
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", "observedAt": "string", "sourceRef": "string" } ], "providers": [ { "id": "string", "label": "string", "status": "string" } ], "rollup": [ { "pillarId": "string", "label": "string", "status": "witnessed", "counted": true, "evidenceClass": "trooth_observation", "observation": "string", "sourceRef": "string", "observedAt": "string", "validity": { "category": "string", "windowDays": 0, "validUntil": "string" }, "freshness": "current", "scope": { "subject": "string", "scope": "company", "area": "string" }, "unknown": [ "string" ] } ], "badge": { "kind": "witnessed", "line": "string", "detail": "string", "asOf": "string", "counted": 0, "witnessed": 0, "staleAreas": [ "string" ], "undatedAreas": [ "string" ], "restrictedAreas": [ "string" ] }, "controls": [], "freshness30d": 0, "disclaimer": "string", "retired": {}}GET /api/compare
Compare companies side by side.
Public Network API, no key · supported
A comparison of published witnessed facts across up to four companies. Control rows are stripped, so a company's internal posture is never exposed through the comparison. comparison is the table a person sees on /compare, decided by the same module: every cell carries one of the states in states, the legend the page prints. A parameter at.<handle>=<n> reads that company at stored record version n, as a dated comparison link does. Handles are compared case-insensitively and a handle named twice is read once; the first four distinct handles are read and any beyond them are ignored. A request that names no handle and no snapshot is refused with 400 (missing_parameter) rather than answered with an empty comparison. Each vendor's pillars have the same shape GET /api/public-mesh/{handle} sends, observedAt and sourceRef included, null when unknown. Rate limited. Readable cross-origin: the response carries Access-Control-Allow-Origin: * and never Access-Control-Allow-Credentials, so a browser on any site may read it without cookies.
curl "https://trooth.co/api/compare"{ "vendors": [ { "company": { "name": "string", "handle": "string", "updatedAt": "string" }, "frameworks": [], "pillars": [ { "id": "see the schema", "label": "see the schema", "status": "see the schema", "observedAt": "see the schema", "sourceRef": "see the schema" } ], "providers": [ { "id": "see the schema", "label": "see the schema", "status": "see the schema" } ], "controls": [] } ], "unavailable": [ "string" ], "frameworkRows": [], "retired": {}, "states": [ { "id": "string", "label": "string", "meaning": "string" } ], "comparison": { "comparedAt": "string", "companies": [ { "key": "string", "state": "listed" } ], "rows": [ { "key": "string", "category": "string", "label": "string", "cells": [ { "state": "see the schema", "label": "see the schema", "flags": "see the schema" } ] } ] }}GET /api/legal/{slug}
A published legal document.
Public Network API, no key · supported
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. Readable cross-origin: the response carries Access-Control-Allow-Origin: * and never Access-Control-Allow-Credentials, so a browser on any site may read it without cookies.
curl "https://trooth.co/api/legal/example-co"{ "slug": "string", "title": "string", "effective": "string", "html": "string"}GET /api/version
The deployment currently serving.
Public Network API, no key · supported
The build stamp of the running deployment. Never cached. Useful for confirming which release answered a request. Readable cross-origin: the response carries Access-Control-Allow-Origin: * and never Access-Control-Allow-Credentials, so a browser on any site may read it without cookies.
None.
curl "https://trooth.co/api/version"{ "stamp": "string"}POST /api/network/batch
Check many companies in one call.
Public Network API, no key · supported
A read of up to 25 companies in one request, for an agent that plans a sequence of steps and needs to know, before acting, what each company's record says and whether it changed since planning. Each target is a domain or profile slug, or { q, pin } where pin is the record digest from an earlier read. Each result has status (listed, not_listed, withheld, unavailable), what Trooth's own readings show, the version and SHA-256 digest of the record GET /api/network/profile serves, pin.changed when a pin was sent, and plain day counts. A target that cannot be read is that target's unavailable, with the problem registry's retryable flag and next action; the other targets are still answered. It changes nothing, so it takes no Idempotency-Key and repeating it is always safe. Nothing in it grades, ranks or certifies a company. Errors are always RFC 9457 problem details. Never cached. Readable cross-origin: the response carries Access-Control-Allow-Origin: * and never Access-Control-Allow-Credentials, so a browser on any site may read it without cookies.
None.
curl -X POST "https://trooth.co/api/network/batch"{ "format": "trooth.network-batch.v1", "checked_at": "string", "summary": { "targets": 0, "listed": 0, "not_listed": 0, "withheld": 0, "unavailable": 0, "changed_since_pin": 0 }, "results": [ { "target": "string", "status": "listed", "problem": { "code": "string", "retryable": true, "next_action": "retry_later", "detail": "string" }, "slug": "string", "name": "string", "domain": "string", "record_url": "string", "profile_url": "string", "witnessed": { "standing": "witnessed", "last_witnessed_at": "string", "first_witnessed_at": "string", "unbroken_since": "string", "checks_passed": 0, "checks_run": 0 }, "record": { "version": 1, "digest": "string", "recorded_at": "string" }, "pin": { "sent": "string", "changed": true }, "facts": { "days_since_last_witnessed": 0, "days_since_profile_updated": 0, "unbroken_readings": 0, "total_readings": 0 } } ], "how_to_read": "string"}GET /api/network/changes
Every record version across the Network.
Public Network API, no key · supported
Every published record version across the Trooth Network after a position, oldest first, at most 100 per page: the company, the version, its SHA-256 digest and the previous one, and where to read it. No record body and no personal fact. Without after or since, the page starts 24 hours back. next is the cursor to send as after, and also the id of the last event, so a client can move between this feed and the event stream without losing its place. Order is by recorded time, ties broken by event id; delivery is at least once across a reconnect (drop repeats by event_id); a company that withdraws its profile stops appearing. A position older than the retention window is 410 beyond_retention. Errors are always RFC 9457 problem details. Never cached. Readable cross-origin: the response carries Access-Control-Allow-Origin: * and never Access-Control-Allow-Credentials, so a browser on any site may read it without cookies.
curl "https://trooth.co/api/network/changes"{ "format": "trooth.network-changes.v1", "events": [ { "id": "string", "type": "record.version", "event_id": "string", "kind": "published", "subject": { "slug": "string", "domain": "string" }, "version": 1, "recorded_at": "string", "digest": "string", "previous_digest": "string", "corrects_version": 1, "record_url": "string", "history_url": "string" } ], "next": "string", "has_more": true, "stream": "string", "guarantees": { "order": "string", "delivery": "string", "withdrawal": "string", "retention_days": 0, "body": "string" }}GET /api/network/changes/stream
Every record version, as Server-Sent Events.
Public Network API, no key · supported
The change feed as a text/event-stream (WHATWG Server-Sent Events). Each event is event: record.version, with the same JSON an entry of GET /api/network/changes carries and id set to its cursor, so a client that reconnects with Last-Event-ID (every EventSource does) resumes exactly after the last event it received. Without Last-Event-ID or after, the stream starts now. A connection is held for about 50 seconds and closed with a comment; the client reconnects (retry: 3000). A comment is sent every 15 seconds on a quiet stream. Errors before the stream opens are RFC 9457 problem details. Readable cross-origin: the response carries Access-Control-Allow-Origin: * and never Access-Control-Allow-Credentials, so a browser on any site may read it without cookies.
curl "https://trooth.co/api/network/changes/stream"GET /api/network/search
Discover companies by several criteria.
Public Network API, no key · supported
Multi-criteria discovery over the same published companies the directory lists: free text, industry, HQ country, data residency region, deployment model, product name, whether Trooth holds a witnessed reading, and profile changes since a date. A company is matched only by facts its public page shows: residency and deployment are searchable only while the company shows the section that renders them. Filters are exact, case-insensitive matches on what the company published; a company that published nothing for a filtered field is absent from the answer, which is not a no. Order is the directory's: witnessed first, then the most recent reading, then name; never by a figure. Each result carries facts, whole days counted from timestamps on the record (days_since_last_witnessed, days_since_profile_updated), which rate nothing. Cursor pagination only; a cursor is bound to the filters it was issued with. An unknown filter is refused. Errors are always RFC 9457 problem details (codes at /api/problems). Cached at the edge for 60 seconds with a 300 second stale-while-revalidate window. Readable cross-origin: the response carries Access-Control-Allow-Origin: * and never Access-Control-Allow-Credentials, so a browser on any site may read it without cookies.
curl "https://trooth.co/api/network/search"{ "format": "trooth.network-search.v1", "results": [ { "slug": "string", "domain": "string", "name": "string", "record_url": "string", "profile_url": "string", "witnessed": true, "last_witnessed_at": "string", "profile_updated_at": "string", "industries": [ "string" ], "products": [ "string" ], "country": "string", "residency": [ "string" ], "hosting": [ "string" ], "facts": { "days_since_last_witnessed": 0, "days_since_profile_updated": 0 } } ], "total": 0, "next_cursor": "string", "partial": { "shown": 0, "total": 0 }, "order": "string", "how_to_read": "string", "consistency": { "mode": "best-effort", "completeEnumeration": false, "note": "string", "cursorScope": "string" }}GET /api/problems
Every error code and whether retrying can help.
Public Network API, no key · supported
The registry every RFC 9457 problem document from trooth.co draws on: each code with its status, fixed title, retryable flag, next action, and terminal (true when repeating the same request cannot change the answer, exactly not retryable). A code keeps its meaning once published. Cached for one hour. Readable cross-origin: the response carries Access-Control-Allow-Origin: * and never Access-Control-Allow-Credentials, so a browser on any site may read it without cookies.
None.
curl "https://trooth.co/api/problems"{ "format": "trooth.problem-catalog.v1", "media_type": "application/problem+json", "next_actions": [ "retry_later" ], "problems": [ { "code": "string", "type": "string", "status": 100, "title": "string", "retryable": true, "next_action": "retry_later", "terminal": true } ], "how_to_read": "string"}Retrieval agents are welcome (robots.txt); model-training crawlers are blocked, since published companies did not consent to wholesale ingestion.
Cite each fact's source, and its date where it has one. When Trooth holds no record, say so.
The Model Context Protocol (MCP) server is live at https://api.trooth.co/public/mcp (JSON-RPC 2.0, public, no key). It leads with 2026-07-28, reports trooth-mcp 1.5.0 and still answers 2025-11-25, 2025-06-18, 2025-03-26 or 2024-11-05.
trooth_public_trust_profile, trooth_outside_in_read, trooth_verify, trooth_ask, trooth_ai_data_use_disclosures, trooth_search_companies and trooth_compare_companies), and 1 more tool that reads only the caller's own workspace with an OAuth access token. Each is read only and returns structuredContent (status, provenance, subject, summary); the table below states each one's access, what it returns, its argument limits and its cost.trooth://methodology, trooth://provenance-labels, trooth://verify-a-vendor, trooth://what-a-call-sends.vendor_trust_check, verify_trust_token, before_you_trust.| Tool | Access | Returns | Arguments (longest accepted, in characters) | Units per call |
|---|---|---|---|---|
trooth_public_trust_profileone company's complete Trust Profile on the Trooth Network, every section with its state and who said it, after a short summary that links the full record | Public, no account | Public record data |
| 1 |
trooth_outside_in_reada live, neutral read of a domain's public surface right now: HTTPS reachability, five common response headers and security.txt | Public, no account | Externally fetched data, read from the named domain at call time |
| 4 |
trooth_verifyre-check the two signatures on a Trust Ledger Token | Public, no account | A signature check of the token you send |
| 1 |
trooth_aska question about Trooth itself, answered from its fixed knowledge base | Public, no account | Trooth's own public documents |
| 1 |
trooth_ai_data_use_disclosureswhat one company has declared about AI and customer data, each row labeled as the company's own declaration | Public, no account | Public record data |
| 1 |
trooth_search_companiessearch the companies published on the Trooth Network by name, domain, tagline or category | Public, no account | Public record data |
| 1 |
trooth_compare_companiestwo to four companies' Trust Profiles side by side, section by section, each cell the company's own record | Public, no account | Public record data |
| 2 |
trooth_my_company_recordyour own workspace's company record, after sign-in | OAuth access token; reads only your own workspace | Caller-private data | None | 1 |
Setup: Agents and MCP. Changes: changelog.
https://api.trooth.co/public/mcp{ "mcpServers": { "trooth": { "type": "http", "url": "https://api.trooth.co/public/mcp" } }}Companies set webhooks in their workspace. Agents and buyers have one public route, POST https://api.trooth.co/public/trust/:slug/agent-subscribe:
webhookUrl and get back a signing secret.trust.posture.changed, signed with x-trooth-signature; workspace endpoints have their own events.Details are on Agents and MCP.
There is no SDK and no scoped Trooth package; any @trooth/ name is not ours. Use plain HTTP and JSON, or generate a typed client from /openapi.json.
One package exists: trooth on npm (source: github.com/troothllc/trooth-cli), with two commands:
trooth check <domain> reads a public record.trooth lint [path] reads what your repository declares and makes no request.It reads no key and calls only the directory endpoint. Anything else attributed to it, trooth scan in particular, was retired and exits with an explanation.
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 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 are announced first in the changelog; policy: API versioning. Availability: status; GET /api/version names the answering deployment.
Questions go to developers@trooth.co.
curl "https://trooth.co/api/version"# { "stamp": "..." }Something wrong or missing? Write to developers@trooth.co; corrections appear in the changelog.
Security reviewers and AI procurement agents read one record, with sources, dates and signatures.