---
title: "Developers: the Trooth API reference - Trooth"
description: "Read any company record from your own code. The public reference: a first working call, authentication, errors, rate limits, pagination, versioning, every endpoint with a request and a response shape, and what to check before production. What is live today, stated plainly."
canonical_url: "https://trooth.co/developers"
markdown_url: "https://trooth.co/developers.md"
generated_from: "the rendered page, converted to Markdown when this was requested"
agent_index: "https://trooth.co/llms.txt"
---

1. [Home](https://trooth.co/)
2. Developers

Reference · Contract 2 · Read only · No key

# Company records your code can read.

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.

- 01Understand what you can buildWhat the interface answers, what it never answers, and where each address points.
- 02Make your first working callFive steps from nothing to a profile read that branches correctly.
- 03Judge whether you can depend on itLimits as numbers, versioning and deadlines, and what to check before production.

## Introduction

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.

### What you can build

- A vendor check inside your own toolResolve a domain to its Trust Profile and show what Trooth witnessed, what the company declared and what is unknown, each labeled as which.
- Search over the NetworkA search box or a typeahead over the same published companies the public directory lists, paged with a cursor.
- An agent that cites its sourcesConnect an assistant to the Model Context Protocol server and let it read a record, with its sources and dates, instead of guessing.
- An alert when a posture changesFollow a published profile and receive a signed event when what Trooth witnessed about it changes.

### What is not offered

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.

Base URLs

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

Client libraries

```
# Nothing to install.curl --version
```

Command line

```
npm install -g trooth
```

## Live today

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

- [Public Trust Profile JSONWitnessed state and the date it was last read, for any listed company.](https://trooth.co/docs/trust-profile-api)
- [Embeddable witnessed badgeA company's live record on its own site in one line.](https://trooth.co/badge)
- [Webhooks for driftPosture changes push to your tools.](https://trooth.co/docs/webhooks)
- [OpenAPI 3.1 contractServed at /openapi.json, generated from the routes and checked on every build.](https://trooth.co/openapi.json)
- [Trooth CLInpx trooth check <domain> reads a public record from the terminal, and trooth lint reads what your own repository declares, locally. The package is trooth on npm. No account required.](https://trooth.co/cli)

Try it nowno account

```
npx trooth check trooth.co
```

Your first working call, in about five minutes

## Quickstart

### Check the prerequisites

1. 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.
### Read a Trust Profile

2. 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.
### Branch on found, not on the status alone

3. 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.
### Read each fact with its origin

4. 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.
### Pin the contract

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

Your first call

```
curl -i "https://trooth.co/api/network/profile?q=trooth.co" \  -H "Trooth-Contract: 2"
```

## Authentication

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.

A public read

```
# No key, no account, no header required.curl "https://trooth.co/api/network/profile?q=example.com"
```

The one header you may sendoptional

```
Trooth-Contract: 2
```

## Errors

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

### Retries, and where they stop

Three rules, and the third is the one that gets left out.

- **Retry only the classes that can change.** `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.
- **Count the attempts and stop.** Back off between them, cap the number, and surface the failure when the cap is reached rather than continuing quietly. Cap the pages in a paging loop too, for the same reason.
- **An outage is not a finding.** A `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.

Status code summary

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

An error body503

```
{  "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."}
```

Retry, with a bound

```
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.");}
```

## Rate limits

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.

Limits

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

The comparison 429no Retry-After

```
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." }
```

## Pagination

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

### The order, and the tie-break

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.

### What the cursor prevents

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.

### What it does not prevent

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

- **An omission.** A company you have not reached yet is witnessed, or witnessed again, during your walk. Its key moves earlier, which is behind your cursor, and you finish the walk without ever seeing it.
- **A duplicate.** A company you already received stops being witnessed during your walk. Its key moves later, which is ahead of your cursor, and you receive it a second time.

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.

### Deduplicating, and resynchronizing

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.

### Expiry, and what the cursor is not bound to

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

### The offset alias, and how to migrate off it

`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](https://trooth.co/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.

Walk every page

```
# 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"
```

## Idempotency

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

Every public operationread only

```
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/version
```

## Versioning

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

- **Both, disagreeing, is refused.** A header and a query parameter naming different contracts is a `400`, not a guess at which you meant.
- **An unsupported version is refused.** A contract this deployment does not answer is a `400` with nothing approximated.
- **Every answer names its contract.** In the `Trooth-Contract` response header, in `contractVersion`, and, for the one you asked for, in `requestedContract`.
- **What moves the number.** Removing or renaming a field, or changing what a value means. Adding a field does not, so read the fields you know and ignore the rest.

### Deprecations and deadlines

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](https://trooth.co/api-versioning), and every deprecation is announced in the [changelog](https://trooth.co/changelog?area=api) with its deadline before it takes effect.

Pin a contract

```
# 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"
```

An unsupported version400

```
{  "found": false,  "error": "unsupported_contract_version",  "message": "This deployment answers contract versions 1, 2 and was asked for 9. ...",  "contractVersion": 2}
```

## Testing

**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](https://trooth.co/schemas/network-profile.v2.schema.json) 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.

Validate against the schema

```
# The schema the route is tested against on every pushcurl -O "https://trooth.co/schemas/network-profile.v2.schema.json"
```

States to cover

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

## The data objects

Four stable objects underpin every Trooth surface. They are versioned conservatively: fields are added, never silently changed.

**Company record**

Identity, products, and witnessed posture for one company: stable slug, domains, and the last-witnessed state.

**Evidence receipt**

One signed reading: source, scope, timestamp, and signature. The unit everything else is built from.

**Provenance label**

A closed vocabulary for where a fact came from: Trooth witnessed, Re-read on a schedule, Integration supplied, Independent third-party, Declared, Public record, Customer reported, Disputed, Stale, Unknown.

**Freshness stamp**

Last witnessed date, expected cadence, and stale behavior, so a reader (human or agent) can weigh a fact by its age.

```
# 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": []}
```

## Reading a record correctly

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.

- **Unknown is never no.** An absent fact means Trooth has no evidence either way. Rendering it as a failing control is wrong and is the most common mistake.
- **Stale is not the same as wrong.** A fact past the refresh window for its category keeps its last value. Weigh it by age rather than discarding it, and take the age from the record's last reading and the category's window rather than from the fact, which carries no date of its own.
- **Declared is not witnessed.** A company saying something and Trooth reading it from a live system are different claims and carry different labels. Keep them distinct wherever you show them.

The full vocabulary and how each state is reached is in the [methodology](https://trooth.co/methodology).

### One fact, in full

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.

### Five dimensions, and they are separate fields

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.

- **Provenance** is `facts[].origin`, one per fact, always present. Trooth reading a live system and a company writing an answer are different claims.
- **Availability** is whether Trooth could read at all, and it has three distinct shapes: `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.
- **Freshness** is `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.
- **Dispute** is `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.
- **Integrity** covers one field of this body and no more. The profile response as a whole is not signed, and a `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](https://trooth.co/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.

### Contract version, and the alias still answered

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.

Five dimensions, five fields

| Dimension | Read it from |
| --- | --- |
| Provenance | `facts[].origin` |
| Availability | `found, a 503, witnessed.standing` |
| Freshness | `witnessed.lastWitnessed, methodology.freshness` |
| Dispute | `facts[].contested, conflicts` |
| Integrity | `witnessStatement` |

## Endpoints

Nine operations, every one a read of published data, and none of them needs a credential. The machine-readable contract is at [/openapi.json](https://trooth.co/openapi.json), OpenAPI 3.1, generated from the source and checked on every build.

- `GET /api/compare`Compare companies side by side
- `GET /api/legal/{slug}`A published legal document
- `GET /api/mobile/directory/featured`Companies with a witnessed record
- `GET /api/mobile/directory/search`Search the Trooth Network
- `GET /api/mobile/directory/vendor/{slug}`One company record
- `GET /api/network/profile`One company's canonical profile and witness facts
- `GET /api/network/suggest`Typeahead over published companies
- `GET /api/public-mesh/{handle}`A company's public evidence rollup
- `GET /api/version`The deployment currently serving

Each operation has its own entry below, with its parameters, the answers it gives and a request you can copy.

Base URL

```
https://trooth.co
```

Endpoints

```
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/version
```

`GET /api/network/profile`

## Retrieve a Trust 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.

### Parameters

**qstring, in queryRequired**

Company domain (example.com) or Trooth slug.

**contractstring, in query**

Pin the response contract version, here or in the Trooth-Contract request header (not both with different values, which is refused with 400). Omit for the current contract. A version this deployment does not answer is refused with 400 rather than approximated. Every response names the contract that served it in the Trooth-Contract response header and in contractVersion, and the one requested in requestedContract; a request for contract 1 is served contract 2 with Deprecation and Sunset headers until 2027-03-01.

### Returns

**200**

The record, or { found: false } when the Network holds none. The body is written to a published JSON Schema and validated against it on every push and after every deployment.

**400**

The q parameter is missing, a contract version this deployment does not answer was requested, or the query and the Trooth-Contract header asked for different contracts.

**503**

Trooth could not read its own record on this load. Retry after the Retry-After interval; nothing is asserted about the company.

Request

```
curl "https://trooth.co/api/network/profile?q=example.com"
```

Response shapefrom /openapi.json

```
{  "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 directory

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.

### Parameters

**qstring, in query**

Search terms. Matched against company name and description.

**industrystring, in query**

Restrict to one industry.

**limitinteger, in query**

Results per page. Clamped to 100; anything unreadable falls back to 25.

**cursorstring, in query**

Opaque page marker. Use next_cursor from the previous response. Preferred over offset.

**offsetinteger, in queryDeprecated**

Zero-based offset. Use next_offset from the previous response. Deprecated in favor of cursor; still answered, with a Deprecation header on the response.

### Returns

**200**

A page of results.

**400**

A cursor that cannot be read.

**503**

The directory could not be read. Not a statement about any company.

Request

```
curl "https://trooth.co/api/mobile/directory/search"
```

Response shapefrom /openapi.json

```
{  "results": [    {      "slug": "string",      "name": "string",      "standing": "Witnessed"    }  ],  "total": 0,  "next_offset": 0,  "next_cursor": "string",  "error": "string"}
```

`GET /api/mobile/directory/featured`

## List witnessed companies

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.

### Parameters

None.

### Returns

**200**

Published companies.

**503**

The directory could not be read.

Request

```
curl "https://trooth.co/api/mobile/directory/featured"
```

Response shapefrom /openapi.json

```
{  "results": [    {      "slug": "string",      "name": "string",      "standing": "Witnessed"    }  ]}
```

`GET /api/mobile/directory/vendor/{slug}`

## Retrieve a company record

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.

### Parameters

**slugstring, in pathRequired**

Company slug, or d--<domain>.

### Returns

**200**

The company record.

**404**

The company is not on the Trooth Network.

**503**

The record could not be read. Not a statement about the company.

Request

```
curl "https://trooth.co/api/mobile/directory/vendor/example-co"
```

Response shapefrom /openapi.json

```
{  "slug": "string",  "name": "string",  "standing": "Witnessed",  "tagline": "string",  "last_scan": "string"}
```

`GET /api/network/suggest`

## Suggest companies

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.

### Parameters

**qstring, in queryRequired**

Partial company name.

### Returns

**200**

Suggestions, possibly none.

Request

```
curl "https://trooth.co/api/network/suggest?q=example.com"
```

Response shapefrom /openapi.json

```
{  "results": [    {      "name": "string",      "domain": "string",      "slug": "string",      "witnessed": true    }  ]}
```

`GET /api/public-mesh/{handle}`

## Retrieve an evidence rollup

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.

### Parameters

**handlestring, in pathRequired**

Company handle.

### Returns

**200**

The rollup, with framework percentages retired (empty) since 2026-09-26.

**404**

No public record for this handle.

**503**

The record could not be read. Not a statement about the company.

Request

```
curl "https://trooth.co/api/public-mesh/example-co"
```

Response shapefrom /openapi.json

```
{  "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

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.

### Parameters

**handlesstring, in queryRequired**

Comma-separated company handles.

### Returns

**200**

The comparison.

**429**

Rate limited. Retry after a short pause.

**502**

No company in the request could be read.

Request

```
curl "https://trooth.co/api/compare?handles=example-co%2Canother-co"
```

Response shapefrom /openapi.json

```
{  "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}`

## Retrieve a legal document

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.

### Parameters

**slugstring, in pathRequired**

Document slug, for example privacy, terms, subprocessors.

### Returns

**200**

The document.

**404**

Unknown document.

Request

```
curl "https://trooth.co/api/legal/example-co"
```

Response shapefrom /openapi.json

```
{  "slug": "string",  "title": "string",  "effective": "string",  "html": "string"}
```

`GET /api/version`

## Retrieve the serving version

The deployment currently serving.

The build stamp of the running deployment. Never cached. Useful for confirming which release answered a request.

### Parameters

None.

### Returns

**200**

The build stamp.

Request

```
curl "https://trooth.co/api/version"
```

Response shapefrom /openapi.json

```
{  "stamp": "string"}
```

## For AI agents

Retrieval agents are welcome and [robots.txt](https://trooth.co/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](https://trooth.co/docs/agents), and changes are announced in the [changelog](https://trooth.co/changelog).

MCP endpointno key

```
https://api.trooth.co/public/mcp
```

Client configuration

```
{  "mcpServers": {    "trooth": {      "type": "http",      "url": "https://api.trooth.co/public/mcp"    }  }}
```

## Webhooks and SDKs

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](https://trooth.co/docs/agents).

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](https://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.

Follow a published profile

```
POST https://api.trooth.co/public/trust/example.com/agent-subscribeContent-Type: application/json{ "webhookUrl": "https://your-service.example/hooks/trooth" }
```

The command-line client

```
npx trooth check example.comnpx trooth lint .
```

## Before you go to production

Six checks, each one a failure an integration has already had somewhere.

1. Handle every class of answerA 200 with found: false, a 400, a 404, a 429 and a 5xx each need their own branch. A 503 is never written down as a finding about a company.
2. Bound retries and pagesRetry 429 and 5xx only, back off, cap the attempts and surface the failure. Cap the pages of a cursor walk and refuse to send a cursor twice.
3. Stay inside the limitsComparison allows 20 requests a minute and 200 an hour from one address. The other reads are served from an edge cache, so polling faster than the cache window returns the same answer.
4. Pin the contract and watch for deprecationSend the contract header, read the deprecation field on every response, and treat a Deprecation or Sunset header as a dated task, not a warning to ignore.
5. Carry provenance and dates to your usersKeep origin next to each value, take age from the record's last reading and the category's window, and never render unknown as no.
6. Know what is promisedThe service level agreement and the API terms state what Trooth commits to and the remedies available. Read both before a decision depends on this interface.

The terms are the [service level agreement](https://trooth.co/sla) and the [API terms](https://trooth.co/api-terms).

Where to watch

| What | Where |
| --- | --- |
| Live availability | [/status](https://trooth.co/status) |
| Incident history, as a feed | [/status/history.rss](https://trooth.co/status/history.rss) |
| API changes, as a feed | [/changelog/rss.xml?area=api](https://trooth.co/changelog/rss.xml?area=api) |
| Security changes, as a feed | [/changelog/rss.xml?type=security](https://trooth.co/changelog/rss.xml?type=security) |
| Versioning policy | [/api-versioning](https://trooth.co/api-versioning) |
| A person | [developers@trooth.co](mailto:developers@trooth.co) |

## Changes and status

Breaking changes to the shape of these responses are announced in the [changelog](https://trooth.co/changelog) before they ship, and the versioning and deprecation policy is at [API versioning](https://trooth.co/api-versioning). Live availability is at [status](https://trooth.co/status), and `GET /api/version` tells you which deployment answered your request.

Questions go to [developers@trooth.co](mailto:developers@trooth.co).

Which deployment answered

```
curl "https://trooth.co/api/version"# { "stamp": "..." }
```

Something on this page wrong, unclear or missing? Write to [developers@trooth.co](mailto:developers@trooth.co). A correction to this reference is recorded in the [changelog](https://trooth.co/changelog?area=api).

## The same record, readable by both.

A security reviewer and an AI procurement agent read the same record, with its sources, its dates and the signature behind each reading.

Make your first call[Verify a signature yourself](https://trooth.co/verify/keys)

## Structured data

```json
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Organization",
      "@id": "https://trooth.co/#org",
      "name": "Trooth",
      "legalName": "Trooth, LLC",
      "alternateName": [
        "Trooth, LLC",
        "Trooth Network",
        "trooth.co"
      ],
      "url": "https://trooth.co",
      "logo": {
        "@type": "ImageObject",
        "@id": "https://trooth.co/#logo",
        "url": "https://trooth.co/brand/trooth-mark_black-on-white_1024.png",
        "contentUrl": "https://trooth.co/brand/trooth-mark_black-on-white_1024.png",
        "width": 1024,
        "height": 1024,
        "caption": "Trooth"
      },
      "image": {
        "@id": "https://trooth.co/#logo"
      },
      "description": "Trooth is an infrastructure and cybersecurity company providing Machine-Readable Trust. The Trooth Network keeps one current, evidence-backed Machine-Readable Trust Profile per company, rechecked on a schedule and signed so it can be replayed.",
      "foundingDate": "2025-12-16",
      "address": {
        "@type": "PostalAddress",
        "streetAddress": "777 Brickell Ave, Suite 500, PMB 1174",
        "addressLocality": "Miami",
        "addressRegion": "FL",
        "postalCode": "33131",
        "addressCountry": "US"
      },
      "contactPoint": {
        "@type": "ContactPoint",
        "contactType": "customer support",
        "email": "hello@trooth.co",
        "url": "https://trooth.co/contact"
      },
      "sameAs": [
        "https://x.com/Troothllc",
        "https://github.com/troothllc",
        "https://www.crunchbase.com/organization/trooth",
        "https://www.wikidata.org/wiki/Q141292994",
        "https://www.youtube.com/@Troothllc",
        "https://www.trustpilot.com/review/trooth.co"
      ]
    },
    {
      "@type": "WebSite",
      "@id": "https://trooth.co/#website",
      "url": "https://trooth.co",
      "name": "Trooth",
      "alternateName": "Trooth Network",
      "inLanguage": "en",
      "publisher": {
        "@id": "https://trooth.co/#org"
      },
      "potentialAction": {
        "@type": "SearchAction",
        "target": {
          "@type": "EntryPoint",
          "urlTemplate": "https://trooth.co/network?q={search_term_string}"
        },
        "query-input": "required name=search_term_string"
      }
    },
    {
      "@type": "ItemList",
      "@id": "https://trooth.co/#sitelinks",
      "name": "Trooth sitelinks",
      "itemListElement": [
        {
          "@type": "SiteNavigationElement",
          "position": 1,
          "name": "Join Trooth now - it's free!",
          "url": "https://trooth.co/signup"
        },
        {
          "@type": "SiteNavigationElement",
          "position": 2,
          "name": "Company, Trooth",
          "url": "https://trooth.co/network/company/trooth"
        },
        {
          "@type": "SiteNavigationElement",
          "position": 3,
          "name": "Trooth Network",
          "url": "https://trooth.co/network"
        }
      ]
    }
  ]
}
```

```json
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://trooth.co/"
    }
  ]
}
```
