@x12i/memorix-explorer-api
v1.37.0
Published
HTTP API for Memorix Explorer — thin adapters over @x12i/memorix-retrieval, with read-only descriptor metadata routes and optional Authix auth.
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 | All reads — inventory, lists, items, narratives, agents, health |
| @x12i/memorix-associator | Associated snapshot property metadata and storage-to-client associated aliases |
| @x12i/memorix-descriptors | List descriptor mutations; entity descriptor narratives catalog + root property catalog compute |
| @x12i/memorix-writer | Record writes via write descriptors |
The React UI lives in @x12i/memorix-explorer (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 | Documentation index | | Consuming Memorix data | Read-only API — snapshots, records, lists, narratives, inventory, Skills cards | | Advanced APIs | Writes, enrichment, associations, metadata, pipeline registry |
Architecture
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 (: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-cataloxmetadata database) - Catalox metadata stored in Mongo (
MEMORIX_CATALOX_DB, defaultmemorix-catalox) — same cluster asMONGO_URI - Optional: OpenRouter or Gemini API key for AI-assisted list suggestions
Quick start
From this package directory:
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 devVerify:
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 setAPI 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 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:
curl -s https://explorer.neociso.com/api/explorer/inventory/summary \
-H "Authorization: Bearer $EXPLORER_API_TOKEN"- Exempt (no token):
GET /health,/api/auth/*, andOPTIONSpreflight. - 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_TOKENis 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
npm run dev # tsx src/cli.ts serve — loads .env from cwd or ../memorix-explorer/.envProduction
npm run build
npm run serve # node dist/cli.js serveCLI (after build or via npx)
npx memorix-explorer-api serveProgrammatic embedding
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.
| 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 |
| 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. OPTIONSreturns 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):
GET /records/collection?entityName=assets&sort=severity:desc&sort=entityId:ascLegacy single sort:
| Param | Alias |
|-------|-------|
| sortProperty | sortPath |
| sortDirection | asc or desc |
Filtering
Repeat filter as property:operator:value:
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:
curl -s "$BASE/health?includeInventory=1"Response highlights:
{
"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 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:
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:
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 |
Example:
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:
# 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→ OTdecisionExploitability) are usually item fields, not list fields — use/records/item(or/records/full?...&contentTypes=decisions). - Prefer
/records/itemfor Skills open; use/records/fullonly 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.
See also docs/consuming-memorix-data.md.
Ad-hoc Assets Exploitability (no Catalox list / item ids)
Same use case without passing Catalox descriptor ids from client code:
# 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.
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. These routes keep storage-compatible names available while presenting associatedInferred as client-facing discovery.
GET /snapshots/{objectType}/{recordId}
Returns a normalized snapshot view:
{
"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 inlinerecordId/contentType. - v2 properties (
associated<ObjectType><ContentType>) use flat copieddata; optional traceability lives in item-level_system.associationwhen enabled. See memorix-pipeline/docs/association-format.md. - Refresh resolves snapshot → snapshot first, then fetches non-snapshot content by matched snapshot
recordIdwhen applicable. - Refresh is one-way: linked content
data→ source snapshotassociated*[]. - Associated arrays do not include linked record
_system,analytics,enrichment, orinsights; query the linked collection directly when those buckets are needed. - Item-level
_systeminside associated arrays is stripped from the default snapshot API view; passinclude=systemto retain it. - Root buckets that do not start with
associatedare 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:
{
"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:
{
"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 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):
{
"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:
{
"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:
{
"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 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):
{
"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.
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):
# 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.
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.
{ "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.
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:
# Terminal 1
npm run dev
# Terminal 2
npm testCatalox seeds
Before first use in a new environment, apply retrieval seeds so entity/list/item descriptors exist:
npm run catalox:seed:memorix-retrieval:applyRequires MONGO_URI and CATALOX_APP_ID (default memorix). Validates with:
npm run catalox:seed:memorix-retrieval:validateMigration: lists catalog rename
If upgrading from a deployment that used the legacy lists catalog id, run once per Catalox app:
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 | Read-only consumption guide | | docs/advanced-apis.md | Writes, pipeline, associations, metadata | | memorix-pipeline docs | Enrichment CLI and association format | | memorix-retrieval README | Underlying read APIs, graph runs, inventory lenses | | memorix-descriptors README | List descriptor shapes and mutations | | memorix-writer README | Write descriptors and record operations | | memorix-explorer integration | How the UI consumes these routes | | MEMORIX-CATALOX-CONTRACTS | Catalog ids and descriptor JSON formats | | MEMORIX-DATABASE-CONVENTIONS | Mongo layout and env resolution |
Package exports
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.
