# @x12i/memorix-explorer-api

> HTTP API for Memorix Explorer — thin adapters over @x12i/memorix-retrieval, with read-only descriptor metadata routes and optional Authix auth.

Latest version **1.37.0** (published 2026-07-19) · 0 weekly downloads

## Install

```sh
npm install @x12i/memorix-explorer-api
pnpm add @x12i/memorix-explorer-api
yarn add @x12i/memorix-explorer-api
bun add @x12i/memorix-explorer-api
```

Provides the command `memorix-explorer-api`.

## Health

**Score 70/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; recently updated; high maintenance score; high quality score.

Warnings: low downloads.

## Facts

| | |
|---|---|
| Version | 1.37.0 |
| Published | 2026-07-19 |
| First published | 2026-07-04 |
| Weekly downloads | 0 |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Node | >=20 |
| Dependencies | 17 |
| Unpacked size | 922.8 KB |
| Known vulnerabilities | 0 (+2 in 1 direct dependencies) |
| Install scripts | no |
| Maintainers | x12i |

## Links

- npm: https://www.npmjs.com/package/@x12i/memorix-explorer-api
- Repository: https://github.com/x12i/memorix-mono-repo
- Homepage: https://github.com/x12i/memorix-mono-repo#readme
- Issues: https://github.com/x12i/memorix-mono-repo/issues
- npm.io page: https://npm.io/package/@x12i/memorix-explorer-api

## Dependencies (17)

- [dotenv](https://npm.io/package/dotenv.md) ^16.4.5
- [fastify](https://npm.io/package/fastify.md) ^5.8.5
- [mongodb](https://npm.io/package/mongodb.md) ^6.12.0
- [@x12i/env](https://npm.io/package/@x12i/env.md) ^4.0.3
- [@x12i/catalox](https://npm.io/package/@x12i/catalox.md) ^5.10.1
- [@fastify/static](https://npm.io/package/@fastify/static.md) ^9.1.3
- [@x12i/memorix-agents](https://npm.io/package/@x12i/memorix-agents.md) ^1.32.0
- [@x12i/memorix-hippox](https://npm.io/package/@x12i/memorix-hippox.md) ^1.33.0
- [@x12i/memorix-writer](https://npm.io/package/@x12i/memorix-writer.md) ^1.32.0
- [@x12i/memorix-catalog](https://npm.io/package/@x12i/memorix-catalog.md) ^1.32.0
- [@x12i/memorix-insights](https://npm.io/package/@x12i/memorix-insights.md) ^1.32.0
- [@x12i/memorix-pipeline](https://npm.io/package/@x12i/memorix-pipeline.md) ^1.32.0
- [@x12i/memorix-resolvers](https://npm.io/package/@x12i/memorix-resolvers.md) ^1.32.0
- [@x12i/memorix-retrieval](https://npm.io/package/@x12i/memorix-retrieval.md) ^1.36.0
- [@x12i/memorix-associator](https://npm.io/package/@x12i/memorix-associator.md) ^1.32.0
- [@x12i/memorix-descriptors](https://npm.io/package/@x12i/memorix-descriptors.md) ^1.37.1
- [@x12i/memorix-simulator-shared](https://npm.io/package/@x12i/memorix-simulator-shared.md) ^1.32.1

## Recent versions

- 1.37.0 (latest) — 2026-07-19
- 1.36.0 — 2026-07-13
- 1.35.0 — 2026-07-12
- 1.34.0 — 2026-07-11
- 1.33.0 — 2026-07-11
- 1.32.1 — 2026-07-11
- 1.32.0 — 2026-07-11
- 1.6.0 — 2026-07-07
- 1.5.0 — 2026-07-07
- 1.4.0 — 2026-07-06
- 1.3.0 — 2026-07-05
- 1.2.0 — 2026-07-05
- 1.1.0 — 2026-07-04

## README

# @x12i/memorix-explorer-api

HTTP API for **Memorix Explorer** — a Fastify server that exposes read and write endpoints over MongoDB and Catalox descriptors.

This package is a thin HTTP layer on top of:

| Package | Role in this API |
|---------|------------------|
| [`@x12i/memorix-retrieval`](../memorix-retrieval/README.md) | All reads — inventory, lists, items, narratives, agents, health |
| [`@x12i/memorix-associator`](../memorix-associator/README.md) | Associated snapshot property metadata and storage-to-client associated aliases |
| [`@x12i/memorix-descriptors`](../memorix-descriptors/README.md) | List descriptor mutations; entity descriptor narratives catalog + root property catalog compute |
| [`@x12i/memorix-writer`](../memorix-writer/README.md) | Record writes via write descriptors |

The React UI lives in [`@x12i/memorix-explorer`](../memorix-explorer/README.md) (sibling package). You can run this API standalone, embed it in another host, or serve the built Explorer UI from the same process.

## Documentation

| Guide | Topic |
|-------|-------|
| [**docs/README.md**](./docs/README.md) | Documentation index |
| [Consuming Memorix data](./docs/consuming-memorix-data.md) | Read-only API — snapshots, records, lists, narratives, inventory, **Skills cards** |
| [Advanced APIs](./docs/advanced-apis.md) | Writes, enrichment, associations, metadata, pipeline registry |

---

## Architecture

```text
Client (browser, curl, automation)
  ↓ HTTP /api/explorer/*
memorix-explorer-api (Fastify)
  ↓ getRetrievalStack()     → @x12i/memorix-retrieval + Catalox + Mongo (Xronox)
  ↓ getDescriptorAdmin()    → @x12i/memorix-descriptors (list writes, narratives catalog, rootPropertyCatalog compute)
  ↓ getWriterClient()       → @x12i/memorix-writer (POST /records/write)
MongoDB cluster (payload + metadata)
  memorix-entities / memorix-events / memorix-knowledge  — record payloads (Xronox)
  memorix-catalox — Catalox descriptor metadata (lists, object types, items)
```

**Descriptor boundary:** list descriptors and **authored narrative catalog entries** on entity descriptors are writable through this API (when `MEMORIX_EXPLORER_ENABLE_METADATA_WRITES=true`). Full Catalox catalog manage + execute lives in [`@x12i/memorix-catalox-api`](../memorix-catalox-api/) (:5180). `GET /catalogs*` on Explorer returns **410 Gone** — use `/api/catalox/catalogs` instead.

---

## Requirements

- **Node.js** ≥ 20
- **MongoDB** reachable via `MONGO_URI` (payload databases + `memorix-catalox` metadata database)
- **Catalox** metadata stored in Mongo (`MEMORIX_CATALOX_DB`, default `memorix-catalox`) — same cluster as `MONGO_URI`
- Optional: **OpenRouter** or **Gemini** API key for AI-assisted list suggestions

---

## Quick start

From this package directory:

```bash
npm install

# Create .env with at least MONGO_URI (and optional MEMORIX_CATALOX_DB; see Environment below).
# In the monorepo you can copy from ../memorix-explorer/.env as a starting point.

# Apply Catalox seeds for entity/list/item descriptors (once per app/environment)
npm run catalox:seed:memorix-retrieval:apply

# Start the API server (default port 5181)
npm run dev
```

Verify:

```bash
curl -s http://127.0.0.1:5181/health                                     # open — no token
curl -s http://127.0.0.1:5181/api/explorer/health \
  -H "Authorization: Bearer $EXPLORER_API_TOKEN" | jq '.ok, .memorixDb'   # token needed if EXPLORER_API_TOKEN is set
```

**API base URL (local):** `http://localhost:5181/api/explorer`
**API base URL (deployed):** `https://explorer.neociso.com/api/explorer`

All routes documented below are relative to that base unless noted. See [Authentication](#authentication) for the bearer token.

---

## Authentication

Two modes — **Authix** (browser + bearer sessions) and **static M2M bearer** — are mutually exclusive guards on `/api/explorer/*`.

### Authix (recommended for deployed UI)

When `AUTHIX_API_URL` and `AUTHIX_API_KEY` are set (or `AUTHIX_AUTH_MODE=app` / `sso`), Authix protects all `/api/*` routes except the public auth allowlist (`/api/auth/login`, `/api/auth/sso/*`, etc.). Browser users log in via `POST /api/auth/login` (password) or SSO; the session is an httpOnly cookie. Machine callers can send `Authorization: Bearer <authix-token>`.

`EXPLORER_API_TOKEN` is **ignored** when Authix is enabled — do not set both unless you intend Authix only.

| Variable | Description |
|----------|-------------|
| `AUTHIX_API_URL` | Authix service base URL (e.g. `https://authix.x12i.com`) |
| `AUTHIX_API_KEY` | Service API key (`x-authix-api-key`) |
| `AUTHIX_AUTH_MODE` | `app` (password login), `sso`, or `disabled` |
| `AUTHIX_ADMIN_USERNAME` / `AUTHIX_ADMIN_PASSWORD` | Operator credentials for `app` mode |
| `AUTHIX_APP_ID` | App id registered in Authix (default `memorix-explorer`) |

### Static M2M bearer (Authix disabled only)

When Authix is **disabled** (`AUTHIX_AUTH_MODE=disabled` and no `AUTHIX_API_URL`), you may set `EXPLORER_API_TOKEN` to require a static bearer on `/api/explorer/*` only:

```bash
curl -s https://explorer.neociso.com/api/explorer/inventory/summary \
  -H "Authorization: Bearer $EXPLORER_API_TOKEN"
```

- **Exempt (no token):** `GET /health`, `/api/auth/*`, and `OPTIONS` preflight.
- **Missing/incorrect token →** `401 { "error": "unauthorized" }`.
- Constant-time compared shared secret — not a JWT. Store in deployment secrets (not in this repo).
- When `EXPLORER_API_TOKEN` is unset, `/api/explorer/*` is open.

**Deployed note:** `explorer.neociso.com` sets `EXPLORER_API_TOKEN` in server environment (outside this repo). If Authix is also configured there, redeploy with this fix so `/api/auth/login` is reachable again.

---

## Running the server

### Development

```bash
npm run dev          # tsx src/cli.ts serve — loads .env from cwd or ../memorix-explorer/.env
```

### Production

```bash
npm run build
npm run serve        # node dist/cli.js serve
```

### CLI (after build or via npx)

```bash
npx memorix-explorer-api serve
```

### Programmatic embedding

```typescript
import {
  createMemorixExplorerApp,
  startMemorixExplorerHttp,
  EXPLORER_API_PREFIX,
} from "@x12i/memorix-explorer-api";

// Start listening
const { url, close } = await startMemorixExplorerHttp({
  port: 5181,
  publicDir: "/path/to/built/explorer-ui", // optional — serves static SPA + fallback
});
console.log(`Listening on ${url}${EXPLORER_API_PREFIX}`);

// Or integrate into an existing Fastify/host setup
const { app } = await createMemorixExplorerApp({ logger: true });
await app.listen({ port: 5181, host: "0.0.0.0" });
```

When `EXPLORER_PUBLIC_DIR` (or `publicDir` option) points at a built Explorer UI (`index.html` present), non-API routes serve the SPA; API routes remain under `/api/explorer`.

---

## Environment variables

### Required

| Variable | Description |
|----------|-------------|
| `MONGO_URI` | MongoDB connection string for the cluster (alias: `MONGO_CONNECTION_STRING`) |

### Mongo target databases

Override only when your deployment uses non-default database names. See [Memorix Database Conventions](../memorix-retrieval/docs/MEMORIX-DATABASE-CONVENTIONS.md).

| Variable | Default | Target |
|----------|---------|--------|
| `MEMORIX_ENTITIES_DB` | `memorix-entities` | Entity records (`target=entity`) |
| `MEMORIX_EVENTS_DB` | `memorix-events` | Event records (`target=event`) |
| `MEMORIX_KNOWLEDGE_DB` | `memorix-knowledge` | Knowledge records (`target=knowledge`, optional) |
| `MEMORIX_OPERATIONAL_DB` | `memorix-operational` | Platform ops (`*-failed`, schema observation, `pipeline-registry-parts`) |
| `MEMORIX_PIPELINE_REGISTRY_DB` | *(falls back to operational)* | Optional override for remote pipeline plugin registry only |
| `MEMORIX_CATALOX_DB` | `memorix-catalox` | Catalox descriptor metadata (lists, object types, items) |

### Catalox

| Variable | Default | Description |
|----------|---------|-------------|
| `CATALOX_APP_ID` | `memorix` | Catalox app id (alias: `MEMORIX_APP_ID`) |

Catalox metadata uses the same `MONGO_URI` cluster; database name defaults to `memorix-catalox`.

### Server

| Variable | Default | Description |
|----------|---------|-------------|
| `EXPLORER_API_PORT` | `5181` | HTTP port (fallback: `PORT`) |
| `EXPLORER_API_TOKEN` | _(unset)_ | Static M2M bearer on `/api/explorer/*` when Authix is **disabled** only. See [Authentication](#authentication) |
| `EXPLORER_PUBLIC_DIR` | `./public` if present | Path to built Explorer static files |

### Optional — AI list suggestions

| Variable | Description |
|----------|-------------|
| `OPENROUTER_API_KEY` | Enables richer confidence on `GET /lists/suggest?mode=ai` |
| `GEMINI_API_KEY` | Alternative AI provider for the same route |

### Smoke / test helpers

| Variable | Default | Description |
|----------|---------|-------------|
| `SMOKE_BASE` | `http://127.0.0.1:5181/api/explorer` | Base URL for smoke scripts |
| `SMOKE_STRICT_INVENTORY` | off | Fail inventory smoke if no rows |
| `SMOKE_STRICT_ENTITIES` | off | Fail entity smoke if graph/records empty |

---

## HTTP conventions

### Base path and methods

- Explorer API routes: **`/api/explorer`** + path (e.g. `/api/explorer/health`).
- Process liveness: **`GET /health`** → `{ "ok": true }` (does not check Mongo/Catalox).
- Supported methods on `/api/explorer/*`: `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`.
- `OPTIONS` returns **204** with no body.
- Responses include **`Cache-Control: no-store`**.
- Request body limit: **1 MB** (JSON bodies for POST/PATCH/PUT).

### Content type

Send JSON bodies with `Content-Type: application/json`. Responses are JSON unless serving static UI.

### Errors

| Status | Meaning |
|--------|---------|
| `400` | Missing/invalid query params or JSON body |
| `401` | Missing/incorrect bearer token (when `EXPLORER_API_TOKEN` is set) — body `{ "error": "unauthorized" }` |
| `404` | Unknown route or missing list/narrative |
| `422` | Write validation failed (`POST /records/write`) |
| `500` | Unexpected server error — body `{ "error": "..." }` |

### Pagination

Shared query parameters for list-style endpoints:

| Param | Alias | Default | Description |
|-------|-------|---------|-------------|
| `limit` | — | `50` | Page size (clamped 1–500) |
| `offset` | `skip` | `0` | Rows to skip |
| `includeTotal` | — | `false` | Include total count in `page.total` when supported |

### Search

| Param | Alias | Description |
|-------|-------|-------------|
| `searchText` | `q` | Full-text search when supported by the underlying list |

### Sorting

**Repeated `sort` params** (preferred for compound sort):

```http
GET /records/collection?entityName=assets&sort=severity:desc&sort=entityId:asc
```

**Legacy single sort:**

| Param | Alias |
|-------|-------|
| `sortProperty` | `sortPath` |
| `sortDirection` | `asc` or `desc` |

### Filtering

Repeat **`filter`** as `property:operator:value`:

```http
GET /records/collection?entityName=assets&filter=severity:gte:7&filter=status:eq:open
```

| Operator | Value notes |
|----------|-------------|
| `eq`, `ne`, `gt`, `gte`, `lt`, `lte` | Scalar comparison |
| `in`, `nin` | Comma-separated list |
| `exists` | `true`/`false` (default true if value omitted) |
| `regex` | String pattern |

### Targets

Many routes accept **`target`**: `entity`, `event`, or `knowledge`. Omit or use `all` where documented to include all configured targets.

---

## API reference

### Health

#### `GET /health`

Memorix-aware health check — Mongo bindings, Catalox discovery, warnings, and optional inventory summary.

| Query | Description |
|-------|-------------|
| `includeInventory` | `1` / `true` — attach inventory summary counts |

**Example:**

```bash
curl -s "$BASE/health?includeInventory=1"
```

**Response highlights:**

```json
{
  "ok": true,
  "generatedAt": "2026-07-01T12:00:00.000Z",
  "mongoUriConfigured": true,
  "memorixDb": "memorix-entities + memorix-events",
  "discoverySample": ["assets", "vulnerabilities"],
  "sourceAware": { "...": "full retrieval health object" },
  "retrieval": {
    "appId": "memorix",
    "entitiesCatalogId": "memorix-object-type-descriptors",
    "discoverySource": "catalox",
    "catalogFallback": false
  },
  "inventorySummary": {
    "orphans": 0,
    "declaredEmpty": 1,
    "matchedPopulated": 12,
    "missingCollections": 0
  },
  "warnings": [],
  "errors": []
}
```

Use this endpoint before debugging empty lists or inventory issues.

---

### Inventory

Unified inventory and entity graph for Explorer navigation.

#### `GET /inventory/collections` · `GET /inventory/summary`

Same handler — returns [`MemorixUnifiedInventory`](../memorix-retrieval/README.md#unified-inventory) rows, issues, and per-target summaries.

| Query | Description |
|-------|-------------|
| `sourceLens` | `catalox-first` (descriptor-first) or `db-first` (Mongo-first). Default: `db-first` |
| `target` | `entity`, `event`, `knowledge`, or `all` |
| `includeExactCounts` | `1` / `true` — run exact `countDocuments` (slower) |
| `includeIgnored` | Include collections marked ignored in inventory policy |

**Example:**

```bash
curl -s "$BASE/inventory/collections?sourceLens=db-first&includeExactCounts=1"
```

Each row includes provenance (`source`, `sources`), suggested UI actions (`actions`), and navigation hints (`navigation.primaryNarrative` when mapped).

#### `GET /inventory/issues`

Subset of unified inventory — `{ issues, summary }` only.

#### `GET /inventory/graph`

Entity relationship graph for the Entities view.

| Query | Default | Description |
|-------|---------|-------------|
| `includeOrphans` | `true` | Include orphan Mongo collections in `orphanEntities` |

**Example:**

```bash
curl -s "$BASE/inventory/graph"
```

Returns `entities`, `orphanEntities`, `memorixDb`, `discovery`, and `catalogRelations`.

---

### Records (read)

#### `GET /records/collection`

Entity collection — descriptor-backed rows, raw Mongo, or auto-detected mode.

| Query | Required | Description |
|-------|----------|-------------|
| `entityName` | Yes | Object type name (e.g. `assets`) |
| `target` | No | `entity` / `event` / `knowledge` |
| `listId` | No | List descriptor id (alias: `listDescriptorId`) |
| `contentType` | No | Restrict to one content type |
| `collectionName` | No | Explicit collection override |
| `mode` | No | `descriptor` \| `raw` \| `auto` (default `auto`) |
| + pagination, sort, filter, search | | See [HTTP conventions](#http-conventions) |

**Example:**

```bash
curl -s "$BASE/records/collection?entityName=assets&mode=auto&limit=10&includeTotal=1"
```

#### `GET /records/raw-collection`

Read Mongo documents directly without list descriptor projection.

| Query | Required |
|-------|----------|
| `target` | Yes |
| `collectionName` | Yes |
| + pagination, sort, search | |

#### `GET /records/full`

Full record across content types (multi-collection compose).

| Query | Required |
|-------|----------|
| `entityName` | Yes |
| One of `recordId`, `entityId`, `eventId`, `knowledgeId` | Yes — prefer `recordId` when the object type uses it |
| `contentTypes` | No — comma-separated; default all declared types |

#### `GET /records/item`

Item descriptor view — sections and fields for a single record.

| Query | Required |
|-------|----------|
| `entityName` | Yes |
| One of `recordId`, `entityId`, `eventId`, `knowledgeId` | Yes — prefer `recordId` when the object type uses it |
| `itemDescriptorId` | No — default item for entity |

#### Skills cards (analysis / decisions) — two generic calls

List ids are **Catalox metadata**, not routes baked into this API. Discover them, then page + open:

```bash
# 0) Discover lists for the entity (ids come from Catalox)
curl -sS "$BASE/lists?entityName=assets" | jq '.[] | {id, title, leadingContentType}'

# 1) Page of cards — use the list id returned above
curl -sS "$BASE/lists/$LIST_ID/records?limit=50&includeTotal=1"

# 2) Open card / Explain Decision
curl -sS "$BASE/records/item?entityName=assets&recordId=$ID"
```

| Platform (stable) | Instance (Catalox `lists` catalog) |
|-------------------|-------------------------------------|
| `GET /lists?entityName=` | Which lists exist, their `id` / `title` / `leadingContentType` |
| `GET /lists/{listId}/records` | Card fields authored on that list descriptor |
| `GET /records/item` | Item sections/fields from `memorix-item-descriptors` |

Pick Skills lists by `leadingContentType` of `decisions` or `analysis` (and UI title). Do **not** hardcode list ids in client code unless you intentionally pin to a known Catalox item for one deployment.

**Notes for clients**

- Nested decision blobs (e.g. assets `data.exploitability` → OT `decisionExploitability`) are usually **item** fields, not list fields — use `/records/item` (or `/records/full?...&contentTypes=decisions`).
- Prefer `/records/item` for Skills open; use `/records/full` only when you need raw multi-CT documents.
- A given deployment may seed example list ids (e.g. neo); treat those as catalog data. Field maps for the neo instance: [`.operational/web-client-intergration/02-skills-card-lists.md`](../.operational/web-client-intergration/02-skills-card-lists.md).

See also [docs/consuming-memorix-data.md](./docs/consuming-memorix-data.md#skills-cards-two-calls).

#### Ad-hoc Assets Exploitability (no Catalox list / item ids)

Same use case without passing Catalox descriptor ids from client code:

```bash
# 1) Page decisions
curl -sS "$BASE/records/collection?entityName=assets&contentType=decisions&limit=50&includeTotal=1"

# 2) Open — exploitability = data.exploitability (default item; or use /records/full)
curl -sS "$BASE/records/item?entityName=assets&recordId=$ID"
# curl -sS "$BASE/records/full?entityName=assets&recordId=$ID&contentTypes=snapshots,decisions"
```

Full field map and paths: [docs/consuming-memorix-data.md § Ad-hoc Assets Exploitability](./docs/consuming-memorix-data.md#ad-hoc-assets-exploitability-no-catalox-list--item-ids).

#### `GET /records/content`

Fetch content for a specific item field (e.g. markdown body, external pointer).

| Query | Required |
|-------|----------|
| `entityName` | Yes |
| `fieldPath` | Yes (alias: `contentKey`) |
| One of `entityId`, `eventId`, `knowledgeId` | Yes |
| `itemDescriptorId` | No |

#### `GET /records/raw-item`

Single Mongo document by id from a named collection.

| Query | Required |
|-------|----------|
| `target` | Yes |
| `collectionName` | Yes |
| `recordId` | Yes |
| `idField` | No — override identity field |

#### `GET /records/workspace`

Cross-entity workspace list (pinned / multi-entity views).

| Query | Description |
|-------|-------------|
| `workspaceListId` | Workspace list descriptor id |
| `entityName` | Filter to one entity |
| + pagination, sort, search | |

---

### Snapshots And Associated Data

Normalized snapshot reads expose the client consumption contract from [the operational guide](../.operational/reports/2026-07-05/client-snapshot-associated-data-consumption-guide.md). These routes keep storage-compatible names available while presenting `associatedInferred` as client-facing discovery.

#### `GET /snapshots/{objectType}/{recordId}`

Returns a normalized snapshot view:

```json
{
  "objectType": "assets",
  "contentType": "snapshots",
  "recordId": "asset-1",
  "concept": { "...": "..." },
  "data": { "...": "native snapshot data" },
  "analytics": { "...": "optional rollups" },
  "enrichment": { "...": "optional sourced values" },
  "insights": { "...": "optional judgments" },
  "system": { "...": "from _system" },
  "associated": {
    "data": [{ "...": "associatedData[]" }],
    "discovery": [{ "...": "associatedInferred[]" }],
    "analysis": [{ "...": "associatedAnalysis[]" }],
    "custom": {
      "associatedAssets": [{ "...": "custom associated copy" }]
    },
    "properties": []
  },
  "storage": {
    "associatedAliases": {
      "associated.data": "associatedData",
      "associated.discovery": "associatedInferred",
      "associated.analysis": "associatedAnalysis"
    }
  }
}
```

| Query | Description |
|-------|-------------|
| `include` | Comma-separated buckets: `data`, `analytics`, `enrichment`, `insights`, `system`, `associated` |
| `idField` | Override lookup field. Defaults to `recordId` |

#### `GET /snapshots/{objectType}/{recordId}/associated`

Returns only the normalized associated view plus associated-property metadata.

#### `GET /snapshots/{objectType}/{recordId}/associated/{propertyName}`

Returns one associated property. `propertyName` may be a storage name or client alias:

| Client/API name | Storage property |
|-----------------|------------------|
| `data` | `associatedData` |
| `discovery` | `associatedInferred` |
| `inferred` | `associatedInferred` |
| `analysis` | `associatedAnalysis` |
| `associatedAssets` or any `associated*` | same storage property |

#### `GET /snapshots/{objectType}/associated-properties`

Returns managed plus discovered associated-property metadata for snapshot records. Discovery samples the snapshot collection and treats any root-level `associated*` property as a valid associated property.

| Query | Description |
|-------|-------------|
| `limit` / `offset` | Sample page for discovery |
| `includeTotal` | Include total count from the raw collection read |

Important semantics:

- Associated arrays are copied read models from linked record `data`.
- **Legacy** properties (`associatedData`, `associatedDiscovery`, …) may include inline `recordId` / `contentType`.
- **v2** properties (`associated<ObjectType><ContentType>`) use flat copied `data`; optional traceability lives in item-level `_system.association` when enabled. See [memorix-pipeline/docs/association-format.md](../../memorix-pipeline/docs/association-format.md).
- Refresh resolves snapshot → snapshot first, then fetches non-snapshot content by matched snapshot `recordId` when applicable.
- Refresh is one-way: linked content `data` → source snapshot `associated*[]`.
- Associated arrays do not include linked record `_system`, `analytics`, `enrichment`, or `insights`; query the linked collection directly when those buckets are needed.
- Item-level `_system` inside associated arrays is stripped from the default snapshot API view; pass `include=system` to retain it.
- Root buckets that do not start with `associated` are still consumable (`analytics`, `enrichment`, `insights`, `_system`) and can be resolver inputs when configured.

#### Association refresh (operations)

| Method | Route | Description |
|--------|-------|-------------|
| POST | `/associations/plan` | Dry-run association plan with fingerprint |
| POST | `/associations/apply` | Apply plan (requires `expectedPlanFingerprint` when configured) |
| POST | `/associations/verify` | Verify associated array shapes on source collection |

---

### Lists (read)

#### `GET /lists?entityName={name}`

Discover list descriptors registered for an entity.

**Response:**

```json
{
  "entityName": "assets",
  "lists": [
    {
      "id": "assets-default",
      "title": "Assets",
      "entity": "assets",
      "leadingContentType": "snapshots",
      "fieldCount": 8
    }
  ]
}
```

#### `GET /lists/{listId}`

Full list descriptor document from Catalox.

#### `GET /lists/{listId}/records`

Execute the list — same pagination, sort, filter, and search conventions as `/records/collection`.

#### `GET /lists/suggest?entityName={name}`

Suggest extensions and analytics fields for list authoring.

| Query | Description |
|-------|-------------|
| `entityName` | Required |
| `listId` | Optional — exclude already-configured extensions/analytics |
| `mode` | `automation` (default) or `ai` (requires API key) |

**Response shape:**

```json
{
  "entityName": "assets",
  "listId": null,
  "mode": "automation",
  "extensions": [
    {
      "kind": "extension",
      "contentType": "analysis",
      "mode": "extendFields",
      "confidence": "high",
      "reason": "Content type \"analysis\" is declared on entity but not joined in this list"
    }
  ],
  "analytics": [
    {
      "kind": "analytics",
      "field": "countAnalysis",
      "collection": "assets-analysis",
      "joinBy": "entityId",
      "metric": { "op": "count" },
      "confidence": "medium",
      "reason": "Count documents in analysis per record"
    }
  ]
}
```

---

### Lists (write)

All list mutations go through `@x12i/memorix-descriptors` `executeDescriptorMutation`. Bodies must match [`MemorixListDescriptor`](../memorix-descriptors/README.md#list-descriptors) validation.

| Method | Path | Operation |
|--------|------|-----------|
| `POST` | `/lists` | Create list descriptor → `201` |
| `PATCH` | `/lists/{listId}` | Update list descriptor |
| `DELETE` | `/lists/{listId}` | Remove list descriptor |
| `POST` | `/lists/{listId}/extensions` | Add extension join |
| `DELETE` | `/lists/{listId}/extensions/{contentType}` | Remove extension |
| `POST` | `/lists/{listId}/analytics` | Add analytics field |
| `DELETE` | `/lists/{listId}/analytics/{field}` | Remove analytics field |
| `PUT` | `/lists/{listId}/sort` | Set compound default sort |

**Create list (`POST /lists`):** pass a full list descriptor object (must include `id`, `entity`, `leadingContentType`, `fields`, etc.). See seed examples under `memorix-descriptors/catalox-seeds/inputs/list-descriptors/`.

**Set sort (`PUT /lists/{listId}/sort`):**

```json
{
  "sort": [
    { "property": "severity", "direction": "desc" },
    { "property": "entityId", "direction": "asc" }
  ]
}
```

Mutation failures return `400` with `{ "error": "...", "result": { ... } }`.

---

### Record writes

#### `POST /records/write`

Write Memorix payload documents via a Catalox **write descriptor**.

**Single record:**

```json
{
  "writeDescriptorId": "asset-analysis-write",
  "entityId": "10.150.68.31",
  "operation": "upsert",
  "input": { "riskLevel": "HIGH", "summary": "..." },
  "metadata": { "source": { "uri": "file://scan.json" } },
  "dryRun": false,
  "validateOnly": false
}
```

**Batch:**

```json
{
  "writeDescriptorId": "asset-analysis-write",
  "operation": "upsert",
  "records": [
    { "entityId": "a", "input": { "...": "..." } },
    { "entityId": "b", "input": { "...": "..." } }
  ],
  "continueOnError": true,
  "dryRun": false
}
```

| Field | Description |
|-------|-------------|
| `writeDescriptorId` | Required — Catalox write descriptor id |
| `operation` | `add`, `upsert`, `patch`, or `replace` |
| `entityId` / `eventId` / `knowledgeId` | Exactly one identity for single writes |
| `input` | Payload object (single write) |
| `records` | Array of record inputs (batch) |
| `dryRun` | Plan without writing |
| `validateOnly` | Validate input only |
| `continueOnError` | Batch — continue after per-record failure |

Returns `200` on success, `422` when validation/write fails. See [`@x12i/memorix-writer`](../memorix-writer/README.md) for descriptor shapes and content uploads.

---

### Narratives

The **authored catalog** lives on entity descriptors at `descriptor.narratives` (validated `MemorixNarrativeDefinition` entries). **Per-record tags** live at `doc.narratives.{key}` (written by pipeline `narrative-sync` or Explorer assignment).

Merged list/detail endpoints combine authored catalog + platform-generated signals + optional live Mongo counts. Use the raw endpoint when you need only the stored descriptor map (e.g. Exellix catalog consumption).

#### `GET /narratives`

Global narrative catalog (merged).

| Query | Description |
|-------|-------------|
| `entity` | Filter to one entity |
| `target` | Filter by target |
| `agentId` | Filter by agent scope |
| `includeLiveCounts` | Attach live record counts |

#### `GET /narratives/{entity}`

Narratives for one entity (merged).

#### `GET /narratives/{entity}/raw`

Authored descriptor map only — `Record<slug, MemorixNarrativeDefinition>` as stored on the entity descriptor (no signal discovery merge).

#### `GET /narratives/{entity}/{key}`

Single narrative (merged enriched summary).

#### `POST /narratives`

Create an authored narrative entry on a descriptor. Requires metadata writes.

**Catalog shape** (`kind` required):

```json
{
  "entity": "assets",
  "key": "has-vulnerabilities",
  "label": "Has vulnerabilities",
  "kind": "having-signal",
  "sourceRef": "assetVulnerabilities",
  "targetEntity": "vulnerabilities"
}
```

**Implementation shape** (legacy condition-based narratives): `label`, `detectionMethod`, `condition`.

#### `PATCH /narratives/{entity}/{key}`

Update an authored entry. Platform-generated keys return `409`.

#### `DELETE /narratives/{entity}/{key}`

Remove from descriptor. Optional `?removeTags=1` clears record tags.

#### `GET /narratives/{entity}/{key}/records`

Paginated records tagged with the narrative.

```bash
curl -s "$BASE/narratives/assets/high-risk/records?limit=20&includeTotal=1"
```

#### Filter any list by `narrativeId`

Narrative tags sit on snapshots (`doc.narratives.{key}`), but the same key filters **lists and other content types** via the virtual filter property `narrativeId` (alias `narrativeKey`):

```bash
# Default list / snapshots
curl -s "$BASE/lists/vulnerabilities-main-list/records?filter=narrativeId:eq:is-vulnerability"

# Analysis (or discovery/decisions) linked to tagged snapshots
curl -s "$BASE/records/collection?entityName=vulnerabilities&contentType=analysis&filter=narrativeId:eq:is-vulnerability&mode=raw"

# User-facing filter dropdown options
curl -s "$BASE/narratives/vulnerabilities/filter-options"

# Execute a dashboard listQuery object
curl -s -X POST "$BASE/records/query" -H 'Content-Type: application/json' \
  -d '{"entityName":"vulnerabilities","listDescriptorId":"vulnerabilities-main-list","filters":[{"property":"narrativeId","operator":"eq","value":"is-vulnerability"}]}'
```

`eq` / `in` = has tag; `ne` / `nin` = missing tag. See [Consuming Memorix data — Narrative filter](./docs/consuming-memorix-data.md#narrative-filter-narrativeid).

---

### Object types & root property catalog

Read-only object-type summaries from Catalox descriptors, plus computed root-envelope property stats.

#### `GET /object-types`

All object-type summaries (`properties`, declarative `rootProperties`, authored `narratives`, `rootPropertyCatalog` when present).

#### `GET /object-types/{name}`

Single object-type summary.

#### `GET /object-types/{name}/root-property-catalog`

Computed `rootPropertyCatalog` array. Optional `?contentType=snapshots` filters to one content type.

#### `POST /object-types/{name}/root-property-catalog/compute`

Full-scan Mongo collections and persist `rootPropertyCatalog` on the descriptor. Requires metadata writes.

```json
{ "ttlMs": 86400000, "contentTypes": ["snapshots"], "force": false }
```

Response includes `skipped` / `reason: "fresh"` when TTL not expired and `force` is false.

---

### Agents

#### `GET /agents`

Returns the agent registry from inventory policy (array of agent objects). Used by Explorer for agent-scoped narrative filtering.

```bash
curl -s "$BASE/agents"
```

---

## Route summary

| Method | Path | Description |
|--------|------|-------------|
| GET | `/health` | Memorix + Mongo + Catalox health |
| GET | `/inventory/collections`, `/inventory/summary` | Unified inventory |
| GET | `/inventory/issues` | Inventory issues only |
| GET | `/inventory/graph` | Entity graph |
| GET | `/records/collection` | Entity collection (descriptor/raw/auto) |
| GET | `/records/raw-collection` | Raw Mongo collection page |
| GET | `/records/full` | Multi-content-type full record |
| GET | `/records/item` | Item descriptor view |
| GET | `/records/content` | Single field content |
| GET | `/records/raw-item` | Raw Mongo document by id |
| GET | `/records/workspace` | Workspace list |
| GET | `/snapshots/{objectType}/{recordId}` | Normalized snapshot view |
| GET | `/snapshots/{objectType}/{recordId}/associated` | Normalized associated arrays |
| GET | `/snapshots/{objectType}/{recordId}/associated/{propertyName}` | One associated property by alias or storage name |
| GET | `/snapshots/{objectType}/associated-properties` | Managed/discovered associated-property metadata |
| POST | `/records/write` | Write record(s) |
| GET | `/lists?entityName=` | List discovery |
| GET | `/lists/suggest?entityName=` | Extension/analytics suggestions |
| GET | `/lists/{listId}` | List descriptor |
| GET | `/lists/{listId}/records` | List rows |
| POST | `/lists` | Create list |
| PATCH | `/lists/{listId}` | Update list |
| DELETE | `/lists/{listId}` | Delete list |
| POST | `/lists/{listId}/extensions` | Add extension |
| DELETE | `/lists/{listId}/extensions/{contentType}` | Remove extension |
| POST | `/lists/{listId}/analytics` | Add analytics |
| DELETE | `/lists/{listId}/analytics/{field}` | Remove analytics |
| PUT | `/lists/{listId}/sort` | Set default sort |
| GET | `/object-types` | Object-type descriptor summaries |
| GET | `/object-types/{name}` | One object-type summary |
| GET | `/object-types/{name}/root-property-catalog` | Computed root envelope property catalog |
| POST | `/object-types/{name}/root-property-catalog/compute` | Recompute and persist root property catalog |
| GET | `/narratives` | Narrative catalog (merged) |
| GET | `/narratives/{entity}` | Entity narratives (merged) |
| GET | `/narratives/{entity}/raw` | Authored narratives map from descriptor |
| GET | `/narratives/{entity}/{key}` | One narrative |
| POST | `/narratives` | Create authored narrative |
| PATCH | `/narratives/{entity}/{key}` | Update authored narrative |
| DELETE | `/narratives/{entity}/{key}` | Delete authored narrative |
| GET | `/narratives/{entity}/{key}/records` | Tagged records |
| GET | `/agents` | Agent registry |

Also: **`GET /health`** at server root (process liveness only).

---

## Commands

| Script | Purpose |
|--------|---------|
| `npm run dev` | Start API server (port 5181) with tsx |
| `npm run build` | Compile TypeScript to `dist/` |
| `npm run serve` | Production server (`node dist/cli.js serve`) |
| `npm test` | Live API smoke tests (server must be running) |
| `npm run test:live` | Same as `npm test` — runs all smoke scripts |
| `npm run smoke:api` | Core health + inventory + narratives smoke |
| `npm run smoke:inventory` | Inventory lenses + raw/descriptor record drill |
| `npm run smoke:records` | Workspace + item fetch |
| `npm run smoke:snapshots` | Normalized snapshot + associated data reads |
| `npm run smoke:narratives` | Narrative catalog + records |
| `npm run smoke:entities` | Graph → collection navigation spine |
| `npm run mongo:inventory` | CLI inventory report (no HTTP server) |
| `npm run catalox:seed:memorix-retrieval:apply` | Apply Catalox retrieval seeds |
| `npm run catalox:seed:memorix-retrieval:validate` | Validate seeds without applying |

**Live test workflow:**

```bash
# Terminal 1
npm run dev

# Terminal 2
npm test
```

---

## Catalox seeds

Before first use in a new environment, apply retrieval seeds so entity/list/item descriptors exist:

```bash
npm run catalox:seed:memorix-retrieval:apply
```

Requires `MONGO_URI` and `CATALOX_APP_ID` (default `memorix`). Validates with:

```bash
npm run catalox:seed:memorix-retrieval:validate
```

---

## Migration: lists catalog rename

If upgrading from a deployment that used the legacy lists catalog id, run once per Catalox app:

```bash
cd ../memorix-descriptors
CATALOX_APP_ID=memorix node scripts/migrate-list-descriptors-catalog.mjs [--dry-run]
```

---

## Related documentation

| Document | Topic |
|----------|-------|
| [docs/consuming-memorix-data.md](./docs/consuming-memorix-data.md) | Read-only consumption guide |
| [docs/advanced-apis.md](./docs/advanced-apis.md) | Writes, pipeline, associations, metadata |
| [memorix-pipeline docs](../memorix-pipeline/docs/README.md) | Enrichment CLI and association format |
| [memorix-retrieval README](../memorix-retrieval/README.md) | Underlying read APIs, graph runs, inventory lenses |
| [memorix-descriptors README](../memorix-descriptors/README.md) | List descriptor shapes and mutations |
| [memorix-writer README](../memorix-writer/README.md) | Write descriptors and record operations |
| [memorix-explorer integration](../memorix-explorer/docs/memorix-explorer-integration.md) | How the UI consumes these routes |
| [MEMORIX-CATALOX-CONTRACTS](../memorix-retrieval/docs/MEMORIX-CATALOX-CONTRACTS.md) | Catalog ids and descriptor JSON formats |
| [MEMORIX-DATABASE-CONVENTIONS](../memorix-retrieval/docs/MEMORIX-DATABASE-CONVENTIONS.md) | Mongo layout and env resolution |

---

## Package exports

```typescript
import {
  createMemorixExplorerApp,
  startMemorixExplorerHttp,
  EXPLORER_API_PREFIX,
} from "@x12i/memorix-explorer-api";
```

Use these to embed the Explorer API in another Node host or serve the UI from the same process. For custom gateways, proxy HTTP to `/api/explorer/*` rather than importing internal handlers.

---
_Source: https://npm.io/package/@x12i/memorix-explorer-api · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
