# API Registry

Local registry of authoritative API and library docs so agents verify integrations against current sources.

Status: active

## Install

```bash
claude plugin marketplace add tyroneross/RossLabs-AI-Toolkit
claude plugin install api-registry@rosslabs-ai-toolkit
```

## The Problem

Integration bugs often start before code is written. An agent remembers an old SDK shape, a renamed model, a deprecated environment variable, or a config path from another framework. The result is plausible code that fails late.

API Registry moves source verification to the front of the workflow. The registry stores approved documentation URLs, refresh logs, and lookup commands so build tools can ask "what docs should I trust for this service?" before writing the integration.

## What I Built

API Registry gives coding agents a local source-of-truth map for API, library, and tool documentation. Before an agent configures Better Auth, Groq, Neon, Vercel, or another integration, it can look up the authoritative docs instead of relying on stale training data.

## Impact

The registry is deliberately local and explicit. It does not try to be a universal search engine. It captures the docs this development environment trusts, keeps them refreshable, and makes source verification cheap enough to use every time an integration appears.

## Architecture

API Registry is a deterministic TypeScript CLI plugin, not a research agent — every script in `scripts/` and `src/` is synchronous logic over SQLite plus a handful of plain HTTP calls (npm, PyPI, GitHub). The only place an LLM enters the loop is documentation curation, and that step is deliberately delegated to the calling Claude session rather than run inside the plugin (see Models below).

Two pipelines matter: **registry population** (turning a project's dependencies into registered services) and **doc lookup** (routing a question to the right source and caching the answer). A third, smaller pipeline — refresh + cooldown — keeps version data current and gates freshly-published packages.

```mermaid
flowchart LR
  subgraph Populate["registry population — scan.ts, deterministic"]
    S1["project dirs: package.json,<br/>requirements.txt, .mcp.json, .env.example"] --> S2[scan.ts walkers]
    S2 --> S3[(scan_candidates table)]
    S3 -->|/api-registry:add, approve| S4[(services table<br/>SQLite WAL — registry.db)]
    S4 --> S5[export-yaml.ts]
    S5 --> S6[registry.yaml mirror]
  end

  subgraph Lookup["doc lookup — one question, traced"]
    Q["/api-registry:docs service query"] --> R[docs.ts route resolver]
    S4 -.getServiceByName.-> R
    R --> D{route}
    D -->|not_registered| STOP["stop — no training-data answer"]
    D -->|"answer_from_cache<br/>(last_checked < 7d)"| CACHE[read cached .md, no network]
    D -->|"verify_then_answer<br/>(last_checked > 7d)"| HC["hashCompare:<br/>GET + SHA-256 vs stored hash"]
    HC -->|unchanged| CACHE
    HC -->|changed| CUR["in-session Claude:<br/>WebFetch + curate section"]
    D -->|"fetch_and_cache<br/>(no cache yet)"| CUR
    D -->|"context7_fallback<br/>(no docs_url registered)"| C7[Context7 MCP query-docs]
    CUR --> WRITE[writeCachedDoc]
    WRITE --> DOCFILE[("~/.api-registry/docs/&lt;service&gt;/&lt;slug&gt;.md")]
    CACHE --> ANS["answer + Source: footer"]
    DOCFILE --> ANS
    C7 --> ANS
  end

  subgraph Refresh["refresh + cooldown — refresh.ts / cooldown.ts"]
    NPM[npm registry] --> RF[refresh.ts]
    PYPI[PyPI JSON API] --> RF
    GH[GitHub Releases API] --> RF
    RF --> S4
    S4 --> CD["cooldown.ts verdict<br/>7d supply-chain dwell"]
  end

  HOOK["SessionStart hook<br/>staleness.ts --marker"] -.detect only, never fetches.-> DOCFILE
```

**Models — which, where, why:**

Models: none. The plugin contains no model call anywhere in `src/` or `scripts/` — routing, hashing, cooldown math, and YAML export are all pure TypeScript. The one point where model reasoning is required — fetching a doc page and extracting the section relevant to a query — is explicitly assigned to the in-session Claude that invoked the command, per the repo's own invariant: "Run an LLM call or background daemon to curate docs" is listed under **Do not** in the plugin's `CLAUDE.md`. Context7 (`mcp__plugin_context7_context7__query-docs`) is called only as a fallback, when a service has no registered `docs_url` — it's an external MCP tool the calling session reaches out to, not a model the plugin hosts or invokes internally.

**Tools & infra — which, why:**

- **better-sqlite3** (synchronous SQLite driver, WAL mode, `~/.api-registry/registry.db`) — source of truth for `services`, `scan_candidates`, `refresh_log`, and `doc_cache`; chosen for a zero-daemon local store with a synchronous API, appropriate for a CLI that runs one command and exits.
- **YAML mirror** (`registry.yaml`, written by `export-yaml.ts`) — human-readable, read-only reflection of the same rows; SQLite stays canonical per the repo's invariant.
- **Markdown doc cache** (`~/.api-registry/docs/<service>/<slug>.md`, YAML frontmatter with `source_url`/`fetched_at`/`last_checked`/`content_hash`) — payload store for curated doc content; git-ignored, greppable without a database client.
- **npm registry / PyPI JSON API / GitHub Releases API** — plain HTTP metadata lookups (`src/http.ts`) that `refresh.ts` and `scan.ts` use for latest-version and publish-date data; no SDKs, just `fetch`.
- **Context7 MCP** — fallback documentation source, reached only when a service has no `docs_url` registered.
- **tsx** — runs the TypeScript scripts directly; commands are markdown files that shell out to `tsx`, no build step.
- **Claude Code plugin runtime** — 8 slash commands, 3 skills (`api-registry`, `docs-search`, `build-loop-bridge`), and one `SessionStart` hook (`session-start-staleness.sh`) that are the plugin's actual surface area.
- **`node --test`** — the test runner (`npm test`), no separate test framework dependency.

## How it works

1. `/api-registry:scan [--path <dir>]` walks project directories (depth ≤ 3, skipping `node_modules`/`.git`) looking for `package.json`, `requirements.txt`, `.mcp.json`, and `.env.example`. Each dependency, MCP server, or matched env-var prefix (`ENV_PREFIX_MAP` — `GROQ`, `ANTHROPIC`, `STRIPE`, `NEON`, etc.) becomes a row in `scan_candidates` with a proposed category and a confidence score (50 without network metadata, 80 with it).
2. Approving a candidate — or running `/api-registry:add <name>` directly — writes a `services` row: `docs_url`, `context7_id`, `package_ids`, `category` (one of 12: auth, db, llm, infra, ui, obs, payments, email, storage, search, protocol, other), `author_owned`, `maintenance_status`. `export-yaml.ts` mirrors the table to `registry.yaml`.
3. When an agent needs docs, `/api-registry:docs <service> <query>` runs `scripts/docs.ts`. It looks up the service, slugifies the query, and checks `doc_cache` for a matching `(service_id, slug)` entry — then emits one of five routes: `not_registered`, `answer_from_cache`, `verify_then_answer`, `fetch_and_cache`, or `context7_fallback`.
4. `not_registered` stops the agent from answering off training data. `answer_from_cache` (fresh, `last_checked` < 7 days) reads the local `.md` file with no network call at all.
5. `verify_then_answer` (cache present but stale) runs `hashCompare`: a `GET` on the registered `docs_url` plus a SHA-256 comparison against the stored `content_hash`. Unchanged content just bumps `last_checked`; changed content triggers re-curation.
6. `fetch_and_cache` (no cache yet) and a changed-content `verify_then_answer` both hand off to the in-session Claude, which `WebFetch`es the doc and curates the section relevant to the query — no external LLM call, per the repo's own rule.
7. The curated result is written via `writeCachedDoc` (frontmatter + body to `~/.api-registry/docs/<service>/<slug>.md`) and indexed via `insertDocCacheEntry` in `doc_cache`. The answer returns with a `Source: <url> (cached/verified <date>)` footer.
8. `context7_fallback` fires only when a service has no `docs_url` registered; it routes to Context7 MCP's `query-docs` instead.
9. `/api-registry:refresh [<name> | --all | --stale]` hits npm, PyPI, or GitHub Releases for the latest version and its publish date, logs the transition to `refresh_log`, and updates `latest_version_released_at`.
10. `cooldown.ts` turns that date into an install verdict: a package released under 7 days ago is `install_blocked: true` (supply-chain dwell time) unless it's `author_owned` (matched against the user's own npm scopes/projects in `~/.api-registry/owned.json`); an unknown release date fails open rather than blocking.
11. A `SessionStart` hook runs `staleness.ts --marker` on every session start — detect-only, it writes `~/.api-registry/staleness.json` and, only if any cached doc exceeds the 7-day threshold, emits one line nudging `/api-registry:refresh --stale`. It never fetches or curates.

## Tech stack

TypeScript on Node ≥ 20, run directly via `tsx` (no build step). `better-sqlite3` (WAL mode) is the sole datastore — `registry.db` for services/candidates/refresh log/doc-cache index, mirrored read-only to `registry.yaml`. Curated doc bodies live as frontmatter'd markdown on disk, not in SQLite. No web framework, no ORM, no vector store: `src/http.ts` is a thin `fetch` wrapper over the npm registry, PyPI JSON API, and GitHub Releases API. Ships as a Claude Code plugin (commands + skills + one `SessionStart` hook); Context7 MCP is the only external service dependency, used solely as a fallback when a service has no registered `docs_url`. Tests run on Node's built-in `node --test`.

## Worked Example

Before wiring up Better Auth in a project, an agent routes the question through the registry instead of answering from training data:

```bash
/api-registry:docs better-auth "what should I trust for session config?"
```

Illustrative shape of the routed response (exact fields vary; the real script also returns `stale_threshold_days` and, on a cache hit, `content_hash`):

```json
{
  "route": "fetch_and_cache",
  "service": "better-auth",
  "display_name": "Better Auth",
  "docs_url": "<registered docs_url>",
  "context7_id": null,
  "file_path": "~/.api-registry/docs/better-auth/what-should-i-trust-for-session-config.md"
}
```

The match comes from a service the registry already knows about — Better Auth, Groq, Neon, and Vercel are among the 26 services seeded in this environment's registry. Lookup latency and refresh-cycle timing aren't measured anywhere in this write-up, so both stay flagged rather than estimated.

## Links

- [HTML project page](https://rosslabs.ai/projects/api-registry/)
- [Demo](https://rosslabs.ai/images/projects/api-registry/cli-demo.gif)
- [Source code](https://github.com/tyroneross/api-registry)
