@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 dev
Verify:
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 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/.env
Production
npm run build
npm run serve # node dist/cli.js serve
CLI (after build or via npx)
npx memorix-explorer-api serve
Programmatic 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:asc
Legacy 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 test
Catalox seeds
Before first use in a new environment, apply retrieval seeds so entity/list/item descriptors exist:
npm run catalox:seed:memorix-retrieval:apply
Requires MONGO_URI and CATALOX_APP_ID (default memorix). Validates with:
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:
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.