---
title: "Offline docs bundle"
description: "The whole documentation corpus as one versioned JSON file, for agents and tools that run without network access."
url: "https://opsinjs.pensievelabs.org/agents/offline-docs-bundle"
source: "https://opsinjs.pensievelabs.org/agents/offline-docs-bundle.md"
section: "Agents & automation"
kind: "guide"
reviewed: "2026-09-20"
reviewer: "engineering"
aliases: ["/r/docs.json", "offline", "air-gapped", "bundle"]
---

> Elements written as `<PascalCase … />` below are opsinjs documentation
> components. Their attributes are the content: the values they render are
> generated from `tokens/*.json` and `registry/catalogue.ts` and are
> published separately at https://opsinjs.pensievelabs.org/r/index.json and under the Reference
> section.
> Nothing is missing from this page. The data simply does not live in
> the prose.

<PageTemplate kind="guide" />

## Overview [#overview]

Not every assistant has a network. Some run inside a locked-down build, some
inside a hospital network that does not resolve arbitrary hostnames, and some
inside a container with egress disabled on purpose. Those are the environments
where a health design system is most likely to be used and least likely to be
reachable.

`/r/docs.json` is one request that returns as much of the corpus as fits inside
a size budget: the processed markdown of each page it carries, that page's
frontmatter, and the docs version identifying the build it came from. It is
capped, and today it is truncated. The response says so about itself. `total`,
`included`, `omitted`, `truncated` and `truncationNote` are top-level fields,
and reading them is the difference between vendoring a corpus and vendoring a
portion of one. Fetch the [shards](./llms-txt.mdx) for the sections it dropped.

```bash
curl -s https://opsinjs.pensievelabs.org/r/docs.json -o opsinjs-docs.json
```

## What is in it [#what-is-in-it]

A wrapper of build and budget metadata, then one entry per page carrying the
fields a machine needs in order to decide whether that page answers the question
in front of it.

```ts
{
  name: string
  version: string          // the docs version this bundle was built from
  generator: string
  docsVersion: string      // the same version, under the name the header uses
  generatedAt: string      // ISO timestamp of the build
  site: string
  implemented: boolean     // whether any component has source
  implementedCount: number
  notice: string           // the one paragraph a machine must not skip
  homepage: string
  format: "processed-markdown"
  characters: number       // the page text carried, in JS characters. The
                           // budget is applied in that same unit
  total: number            // pages in the corpus
  included: number         // pages in this response
  omitted: number          // pages the budget dropped
  truncated: boolean
  truncationNote?: string  // present only when truncated
  sections: Array<{…}>     // the sixteen sidebar groups
  shards: Array<{…}>       // where to fetch what was dropped
  componentCounts: Record<string, number>        // component pages per phase,
                                   // of the included pages
  corpusComponentCounts: Record<string, number>  // the same, over the whole
                                   // corpus
  conventions: {…}         // the twin, catalog, roster and search URLs
  pages: Array<{
    url: string            // canonical page URL
    markdownUrl: string    // the same page's .md twin
    slug: string[]
    section: string
    title: string
    description: string
    status?: string        // planned | shipped | deprecated. Component pages
                           // only; absent on every other kind
    kind: string           // component | health | foundation | …
    evidence?: string      // health pages declare this
    aliases: string[]
    implements: string[]
    governedBy: string[]
    usedIn: string[]
    reviewed?: string
    markdown: string       // processed markdown; documentation components
                           // arrive as <PascalCase … /> tags
  }>
}
```

The page body is `markdown`, not `content`, and `markdown` is the same processed
text the `.md` twins serve. "Processed" is narrower than it sounds: remark has
run, so imports are gone and tables and code blocks are markdown, but the
documentation components are not rendered. `<ComponentPreview name="range-bar" />`
arrives as that tag and its attributes are the content. See
[Raw markdown API](./raw-markdown-api.mdx) for what survives and what does not.
A page inside the bundle also omits the per-page block quote that a standalone
twin carries about those tags, so nothing in a page body explains them; this
paragraph is the explanation.

`componentCounts` tallies the component pages this response carried and
`corpusComponentCounts` tallies every component page in the corpus. Neither
sums to `included` or to `total`, and that is not an off-by-one: a release
phase belongs to a component, so every page that is not a component page
declares none and is counted in neither tally. Read `corpusComponentCounts` for a question about
opsinjs and `componentCounts` for a question about this response.

The bundle carries the documentation, not the code. Component source lives in
the registry items. `/r/<name>.json` is the item for one of the sixty built
components, and the catalog is `/r/registry.json`. Fetch either separately.

## Size and freshness [#size-and-freshness]

The bundle is most of a corpus and it is not small. Two habits keep it usable:

**Index it, do not paste it.** It is meant to be searched, chunked or embedded
locally. Putting the entire file in a context window spends the window on the
retrieval problem you were trying to avoid. If your tool wants a subset, the
[shards](./llms-txt.mdx) exist for exactly that and are much cheaper.

**Pin the version, then check it.** The `version` field is the point of the
whole file. A vendored bundle is a snapshot, and a snapshot that nobody ever
compares to the live version quietly becomes wrong. A weekly job that fetches
the few bytes of the current version string and warns on a mismatch is enough.
Compare `included` and `total` at the same time: the set of pages a snapshot
carries moves as the corpus grows, so two bundles at the same version are the
same text and two at different versions may not even be the same pages.

<Callout>
  The most important field on a component page in this bundle is `status`. A built
  component reads `shipped` and the source is in its registry item, and today that
  covers every component. Every opsinjs component has been audited against WCAG 2.2
  AA by its own authors (see [ADR 0025](../project/decisions/0025-the-audit-is-author-run.mdx)),
  which is not an independent accessibility review, and no component has had a
  clinical review. `shipped` means the source installs, and it does not mean an
  independent review or a clinical review has happened. Nothing here is for a
  production health surface until a clinician signs it. An agent
  working from a stale bundle a year from now must be able to discover that the
  world has moved. The docs version is how it finds out, and `status` is what will
  have changed.
</Callout>

## Using it in an air-gapped build [#using-it-in-an-air-gapped-build]

<Steps>
  ### Vendor it deliberately [#vendor-it-deliberately]

  Commit it, or store it as a build artefact with a checksum. A file fetched at
  build time from a network you do not control is not an offline strategy.

  ### Record where it came from [#record-where-it-came-from]

  Keep `version` and `generatedAt` visible to whatever consumes the bundle, and
  surface them in any answer the tool produces. "According to opsinjs docs version
  X" is a checkable claim; "according to opsinjs" is not.

  ### Refresh on a schedule you own [#refresh-on-a-schedule-you-own]

  Treat it like a dependency: a deliberate update with a diff, not a silent
  refresh.

  ### Fail loudly when it is missing [#fail-loudly-when-it-is-missing]

  A tool that falls back to the model's general knowledge when the bundle is
  absent will produce confident, invented answers about a health design system.
  Prefer an error.
</Steps>

## Verify it worked [#verify-it-worked]

<Steps>
  ### It parses, and you know what it left out [#it-parses-and-you-know-what-it-left-out]

  ```bash
  node -e "const d=require('./opsinjs-docs.json'); console.log(d.version, d.included, 'of', d.total, d.truncated ? '(truncated)' : '')"
  ```

  `included` below `total` is the budget, not a corrupt download.
  `truncationNote` says so in a sentence and the `shards` array is where to fetch
  the rest.

  ### A known page is present with its status [#a-known-page-is-present-with-its-status]

  Look up a component page and confirm it carries a `status`, which reads
  `shipped` for a built component. A component page present with no `status` is a
  bug worth reporting, and a page that is not a component carries none by design. A
  page absent altogether is usually the cap. Check `omitted` before reporting
  anything.

  ### The page bodies are under `markdown` [#the-page-bodies-are-under-markdown]

  Read `pages[0].markdown`. It is markdown, and it will contain `<PascalCase … />`
  tags; that is the format rather than a fault. Reaching for `pages[0].content`
  returns `undefined`, which is the quietest way a vendored reader ends up
  indexing an empty corpus.
</Steps>

## Troubleshooting [#troubleshooting]

**The download is truncated.** Check `truncated` and `omitted` in the payload
first: the server caps this response on purpose and reports the cap in
`truncationNote`. A proxy body-size limit is only the likely cause if the JSON
will not parse at all. In that case, prefer a client that streams to disk.

**The bundle disagrees with the site.** Compare version strings. If they match
and the content differs, that is a real bug and worth reporting.

**I only need the components.** Use `/llms-components.txt`, which is a fraction
of the size.

**I need this in an air-gapped environment where even the initial fetch is
impossible.** Build it yourself: clone the repository and run the site's own
generate step. The bundle is produced from the same source you would be cloning.

## Next [#next]

* [llms.txt and the shards](./llms-txt.mdx) covers smaller, cheaper subsets.
* [Machine-readable schemas](./machine-readable-schemas.mdx) covers the
  structured data the bundle does not include.
* [Raw markdown API](./raw-markdown-api.mdx) serves one page at a time.
