# The public data model

Five shapes cross the public boundary: the page record, the corpus status object, the capability status manifest, the error envelope, and the event envelope from the published webhook contract. Everything else — people, companies, opportunities, follow-ups — is a product concept with documentation pages, not an object any public endpoint returns.

- **Status:** Available
- **Audience:** developer
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/developers/data-model-public/

## The page record

This is the one you will handle most. It is returned by search (with a `score` added), by the list route, and by the page route (with `related` and, when asked, `body` added). One shape everywhere, because one function builds it.

```json
{
  "id": "D0032",
  "kind": "developer",
  "url": "https://connectbyjbrh.com/developers/data-model-public/",
  "markdown": "https://connectbyjbrh.com/developers/data-model-public/index.md",
  "title": "The public data model",
  "description": "…",            // the meta description
  "summary": "…",                // the answer-first paragraph
  "status": "available",         // one of five words
  "audiences": ["developer"],    // owner | customer | both | developer
  "channels": [],                // empty unless the page is channel-specific
  "updated": "2026-09-10",
  "related": ["/developers/entity-ids/", "…"],   // page route only
  "body": "# The public data model\n…",          // page route, body=true
  "body_format": "markdown",                      // or "unavailable"
  "score": 12.804                                 // search route only
}
```

`related` holds site-relative paths while `url` and `markdown` are absolute. That asymmetry is worth handling once in your client rather than discovering it in a link resolver.

## The other four

| Shape | Route | Fields |
|---|---|---|
| Corpus status | `/api/public/docs/status` | `product`, `operator`, `version`, `generated`, `statuses`, `capabilities`, `documentation.pages`, `documentation.by_kind` |
| Capability manifest | `/docs-data/status.json`, or `/api/public/docs/manifest/status` | `statuses` — the vocabulary — and `capabilities` — the map of capability to status |
| Error envelope | Any failing public route | `error.code`, `error.message`. Codes seen here: `rate_limited`, `not_found` |
| Event envelope | The published webhook contract | `id`, `type`, `occurredAt`, `workspaceId`, `data` |

The error envelope is always the same object, which means a client can parse failures without branching on the route it called. `error.code` is a stable string to switch on; `error.message` is prose for a person and may be reworded.

> **Note** The MCP transport wraps these rather than replacing them. A tool result carries a `content` array for a model to read and a `structuredContent` object holding the same JSON for a program — so the shapes above are what you get either way.

## How they relate

- A **page record** points at its Markdown alternate by URL and at other pages by path. Nothing points back; the graph is one-directional and cycles are ordinary.
- The **capability manifest** and a page record meet on the status word. A page's `status` is one of the five keys in the manifest's `statuses` map, which is how a client can render the word with its full definition.
- The **corpus status** object embeds the capability manifest's two maps, so one request gets both the vocabulary and the per-capability values.
- The **event envelope** shares nothing with the documentation shapes. Its `workspaceId` is workspace data, and no documentation object carries one.

There is no pagination object anywhere in this list. The list route takes a `limit` up to 1,000 and returns a plain array with a `count`; the corpus is small enough that a cursor would be ceremony.

## What is deliberately not a public object

Connect's own records — Person, Company, Identity, Opportunity, Support Case, Follow-up, Memory, Knowledge, Fact — are real, documented concepts with pages of their own under [relationships](/docs/relationships/) and elsewhere. None of them is returned by a public endpoint, and none has a published field list in this corpus.

That is a boundary, not an omission. Those records live inside a workspace behind three independent layers of isolation, and publishing a field list would create the expectation of an endpoint that does not exist. The only place workspace-shaped data appears in any public description is the event envelope's `data` object — and that carries identifiers and a reason, not a record.

The practical consequence for a client: if you need business data, you are not on the public developer surface, and the documentation you want is the product documentation rather than this section.

## Questions

### Will new fields appear on a page record?

They can, and they are additive. Ignore what you do not recognise. Nothing documented here is removed without the change appearing in the changelog.

### Why does the search result carry `score` but the list result not?

Because a list has no query to be relevant to. The list route returns records in path order, which is stable and makes a diff between two fetches readable.

### Is `body_format` ever anything other than `markdown`?

It is `unavailable` when the Markdown alternate could not be read. The `body` is then an empty string rather than absent, so a client that reads both fields can tell a missing representation from an empty page.

## Related

- [Stable identifiers](https://connectbyjbrh.com/developers/entity-ids/)
- [The machine-readable documentation](https://connectbyjbrh.com/developers/machine-manifests/)
- [Searching the documentation programmatically](https://connectbyjbrh.com/developers/docs-search-api/)
- [The product status manifest](https://connectbyjbrh.com/developers/status-manifest/)
- [The AsyncAPI description](https://connectbyjbrh.com/developers/asyncapi-events/)

## What this page is based on

- `backend/app/public_docs.py` — `Doc.public`, `status`, `data_file`
- `backend/app/public_developer_api.py` — `_error`, `_ok`, route payloads
- `webapp/developers/asyncapi.yaml` — the Event schema
- `backend/app/mcp_server.py` — `_text` and structuredContent
