Docs Navigation

API Reference

Every endpoint on the Nocturnus.AI HTTP server. Run the smoke test below to verify your setup, then explore context management and the simplified API. Base URL: http://localhost:9300


Common Headers

  • Content-Type: application/json — required for all POST requests
  • X-Database: default — selects the logical database (optional, defaults to "default")
  • X-Tenant-ID: <name> — selects the tenant within that database (required on most endpoints; MCP routes default to "default")
  • Authorization: Bearer <key> — required when auth is enabled

Error Responses

All endpoints return errors as JSON with a consistent shape:

{
  "code": "VALIDATION_ERROR",
  "message": "Missing required header: X-Tenant-ID",
  "details": null
}

Common error codes: VALIDATION_ERROR, NOT_FOUND, CONFLICT, UNAUTHORIZED, RATE_LIMITED.

API Smoke Test (Copy/Paste)

Use this first. It validates /tell, /teach, and /ask directly from the API reference.

export NOCTURNUS_BASE_URL=http://localhost:9300
export NOCTURNUS_DB=default
export NOCTURNUS_TENANT=default

curl -sS "$NOCTURNUS_BASE_URL/health"

## Tell two parent facts (need a chain for grandparent to work)
curl -sS -X POST "$NOCTURNUS_BASE_URL/tell" \
  -H "Content-Type: application/json" \
  -H "X-Database: $NOCTURNUS_DB" \
  -H "X-Tenant-ID: $NOCTURNUS_TENANT" \
  -d '{"predicate":"parent","args":["alice","bob"]}'

curl -sS -X POST "$NOCTURNUS_BASE_URL/tell" \
  -H "Content-Type: application/json" \
  -H "X-Database: $NOCTURNUS_DB" \
  -H "X-Tenant-ID: $NOCTURNUS_TENANT" \
  -d '{"predicate":"parent","args":["bob","charlie"]}'

## Teach a rule: grandparent(?x,?z) :- parent(?x,?y), parent(?y,?z)
curl -sS -X POST "$NOCTURNUS_BASE_URL/teach" \
  -H "Content-Type: application/json" \
  -H "X-Database: $NOCTURNUS_DB" \
  -H "X-Tenant-ID: $NOCTURNUS_TENANT" \
  -d '{
  "head": {"predicate":"grandparent","args":["?x","?z"]},
  "body": [
    {"predicate":"parent","args":["?x","?y"]},
    {"predicate":"parent","args":["?y","?z"]}
  ]
}'

## Ask: should return grandparent(alice, charlie)
curl -sS -X POST "$NOCTURNUS_BASE_URL/ask" \
  -H "Content-Type: application/json" \
  -H "X-Database: $NOCTURNUS_DB" \
  -H "X-Tenant-ID: $NOCTURNUS_TENANT" \
  -d '{"predicate":"grandparent","args":["?who","charlie"]}'

Context Management (cost optimization)

Endpoint Description
POST /context Simplest API — turns in, facts out. Array of strings → optimized facts. Requires LLM provider.
POST /context/optimize Deprecated (sunset 2026-07-01) — use POST /memory/context with goals instead
POST /context/diff Incremental changes since last context window
POST /context/summary Knowledge base summary (predicate counts, contradictions)
POST /context/session/clear Clear diff session state
POST /context/ingest Extract + assert + optimize in one call (requires LLM)
New to NocturnusAI? Run the smoke test above — it uses /tell, /teach, and /ask which work immediately with no LLM config. For the full turn-reduction workflow with POST /context, you'll need an LLM provider configured. See the context workflow guide →

Simplified API

Developer-friendly aliases for the most common operations. These are the endpoints shown in the quickstart.

Endpoint Full Form Description
POST /tell POST /assert/fact Store a fact
POST /ask POST /infer Query with inference (backward chaining)
POST /teach POST /assert/rule Define a logical rule
POST /forget POST /retract Remove a fact (cascading)

POST /assert/fact (or /tell)

Assert a fact into the knowledge base.

cURL

curl -X POST http://localhost:9300/assert/fact \
  -H "Content-Type: application/json" \
  -H "X-Tenant-ID: default" \
  -d '{"predicate":"parent","args":["alice","bob"]}'

Request

{
  "predicate": "parent",
  "args": ["alice", "bob"],
  "truthVal": true,
  "scope": null,
  "ttl": null,
  "validUntil": null,
  "metadata": {}
}

Only predicate and args are required. All other fields are optional.

Response

Returns plain text (not JSON):

Fact Asserted: parent(alice, bob)

POST /assert/rule (or /teach)

Define a logical rule for inference.

cURL

curl -X POST http://localhost:9300/assert/rule \
  -H "Content-Type: application/json" \
  -H "X-Tenant-ID: default" \
  -d '{
    "head": {"predicate":"grandparent","args":["?x","?z"]},
    "body": [
      {"predicate":"parent","args":["?x","?y"]},
      {"predicate":"parent","args":["?y","?z"]}
    ]
  }'

Request

{
  "head": { "predicate": "grandparent", "args": ["?x", "?z"] },
  "body": [
    { "predicate": "parent", "args": ["?x", "?y"] },
    { "predicate": "parent", "args": ["?y", "?z"] }
  ],
  "scope": null
}

POST /infer (or /ask)

Run backward-chaining inference. Use ?-prefixed variables for unknowns.

cURL

curl -X POST http://localhost:9300/infer \
  -H "Content-Type: application/json" \
  -H "X-Tenant-ID: default" \
  -d '{"predicate":"grandparent","args":["?who","charlie"]}'

Request

{
  "predicate": "grandparent",
  "args": ["?who", "charlie"],
  "scope": null
}

To get the full derivation chain showing how each result was derived, use POST /ask (the simplified alias) which accepts withProof in the JSON body:

{
  "predicate": "grandparent",
  "args": ["?who", "charlie"],
  "withProof": true
}

Response

[
  {
    "predicate": "grandparent",
    "args": ["alice", "charlie"],
    "negated": false,
    "scope": null,
    "metadata": {},
    "createdAt": null,
    "validFrom": null,
    "validUntil": null,
    "ttl": null,
    "confidence": null
  }
]

POST /query

Direct pattern matching without inference (no rule application). Reads the Hexastore directly — faster than /infer when you only need stored facts and don't need derived conclusions.

cURL

curl -X POST http://localhost:9300/query \
  -H "Content-Type: application/json" \
  -H "X-Tenant-ID: default" \
  -d '{"predicate":"parent","args":["?who","bob"]}'

Request

{
  "predicate": "parent",
  "args": ["?who", "bob"],
  "scope": null
}

Response

[
  { "predicate": "parent", "args": ["alice", "bob"], "truthVal": true }
]

POST /retract (or /forget)

Remove a fact. The TMS automatically cascade-retracts any derived conclusions that depended on it.

cURL

curl -X POST http://localhost:9300/retract \
  -H "Content-Type: application/json" \
  -H "X-Tenant-ID: default" \
  -d '{"predicate":"parent","args":["alice","bob"]}'

Request

{
  "predicate": "parent",
  "args": ["alice", "bob"]
}

POST /aggregate

Run aggregation operations over matched facts. Supports COUNT, SUM, MIN, MAX, and AVG.

cURL

curl -X POST http://localhost:9300/aggregate \
  -H "Content-Type: application/json" \
  -H "X-Tenant-ID: default" \
  -d '{"predicate":"salary","op":"AVG","argIndex":1}'

Request

{
  "predicate": "salary",
  "op": "AVG",
  "argIndex": 1,
  "scope": null
}

op is one of: COUNT, SUM, MIN, MAX, AVG. argIndex selects which argument to aggregate over (required for numeric ops, not needed for COUNT). scope is optional.

Response

{ "result": 75000.0 }

POST /assert/facts (Bulk Assert)

Assert multiple facts in a single request.

cURL

curl -X POST http://localhost:9300/assert/facts \
  -H "Content-Type: application/json" \
  -H "X-Tenant-ID: default" \
  -d '{"facts":[
    {"predicate":"role","args":["alice","engineer"]},
    {"predicate":"role","args":["bob","manager"]}
  ]}'

Request

{
  "facts": [
    { "predicate": "role", "args": ["alice", "engineer"] },
    { "predicate": "role", "args": ["bob", "manager"] }
  ]
}

POST /retract/pattern

Retract all facts matching a predicate pattern. Use null in the args array as a wildcard.

cURL

curl -X POST http://localhost:9300/retract/pattern \
  -H "Content-Type: application/json" \
  -H "X-Tenant-ID: default" \
  -d '{"predicate":"role","args":[null,"engineer"]}'

Request

{
  "predicate": "role",
  "args": [null, "engineer"],
  "scope": null
}

This retracts all facts with predicate role where the second argument is "engineer", regardless of the first argument.


ACID Transactions

Group multiple operations into an atomic unit. If any operation fails, the entire transaction rolls back — ensuring consistency when multiple agents write simultaneously.

# Common headers used below
export NOCTURNUS_TENANT=default
export NOCTURNUS_DB=default

# 1. Begin transaction (/tx/begin returns plain text transaction id)
TX_ID=$(curl -s -X POST http://localhost:9300/tx/begin \
  -H "X-Database: $NOCTURNUS_DB" \
  -H "X-Tenant-ID: $NOCTURNUS_TENANT")

# 2. Assert facts within the transaction
curl -X POST http://localhost:9300/assert/fact \
  -H "Content-Type: application/json" \
  -H "X-Database: $NOCTURNUS_DB" \
  -H "X-Tenant-ID: $NOCTURNUS_TENANT" \
  -H "X-Transaction-ID: $TX_ID" \
  -d '{"predicate":"balance","args":["alice","500"]}'

curl -X POST http://localhost:9300/assert/fact \
  -H "Content-Type: application/json" \
  -H "X-Database: $NOCTURNUS_DB" \
  -H "X-Tenant-ID: $NOCTURNUS_TENANT" \
  -H "X-Transaction-ID: $TX_ID" \
  -d '{"predicate":"balance","args":["bob","300"]}'

# 3. Commit (or rollback)
curl -X POST "http://localhost:9300/tx/commit/$TX_ID" \
  -H "X-Database: $NOCTURNUS_DB" \
  -H "X-Tenant-ID: $NOCTURNUS_TENANT"
# curl -X POST "http://localhost:9300/tx/rollback/$TX_ID" \
#   -H "X-Database: $NOCTURNUS_DB" \
#   -H "X-Tenant-ID: $NOCTURNUS_TENANT"

POST /extract

LLM-powered fact extraction from natural language text. See LLM Integration for details.

cURL

curl -X POST http://localhost:9300/extract \
  -H "Content-Type: application/json" \
  -H "X-Tenant-ID: default" \
  -d '{"text":"Acme Corp is on the enterprise plan.","assert":true}'

Request

{
  "text": "Acme Corp is on the enterprise plan.",
  "assert": true,
  "rules": false,
  "scope": null,
  "context": null
}

POST /synthesize

LLM-powered natural language question answering grounded in facts. See LLM Integration.

cURL

curl -X POST http://localhost:9300/synthesize \
  -H "Content-Type: application/json" \
  -H "X-Tenant-ID: default" \
  -d '{"question":"What plan is Acme Corp on?"}'

Request

{
  "question": "What plan is Acme Corp on?",
  "scope": null
}

Response

{
  "answer": "Acme Corp is on the enterprise plan.",
  "derivation": [{ "fact": "subscription_tier(acme_corp, enterprise)", "type": "fact_match" }],
  "confidence": 0.95,
  "queriesExecuted": ["subscription_tier(acme_corp, ?tier)"],
  "provider": "anthropic",
  "model": "claude-sonnet-4-20250514"
}

Memory Endpoints

POST /memory/context (unified context endpoint)

The single entry point for all context window operations. Simple requests (just maxFacts/minSalience) use a fast salience-ranked path. Adding goals, sessionId, or relevanceBuckets automatically triggers the full optimization engine.

Simple usage (fast path)

curl -X POST http://localhost:9300/memory/context \
  -H "Content-Type: application/json" \
  -H "X-Tenant-ID: default" \
  -d '{
    "maxFacts": 100,
    "minSalience": 0.0
  }'

Advanced usage (goal-driven optimization)

curl -X POST http://localhost:9300/memory/context \
  -H "Content-Type: application/json" \
  -H "X-Tenant-ID: default" \
  -d '{
    "maxFacts": 25,
    "goals": [{"predicate":"enforceable","args":["contract_001"]}],
    "sessionId": "session-42",
    "format": "natural",
    "includeRules": true,
    "autoResolveContradictions": true,
    "relevanceBuckets": [
      {"name":"legal","predicates":["enforceable","statute_of_frauds"],"weight":3.0}
    ]
  }'

Request fields

Field Type Default Description
maxFacts int? 100 Maximum facts in the context window
minSalience double? 0.0 Minimum salience score filter
predicates string[]? null Filter to specific predicate names
scope string? null Restrict to facts in this scope
format string? "structured" "natural" returns pre-formatted formattedText for LLM prompts; "structured" returns entries array
includeRules boolean? false Include relevant rules in the response
goals GoalSpec[]? null Goal atoms for backward chaining (triggers optimization engine)
sessionId string? null Session ID for incremental diffs (triggers optimization engine)
autoResolveContradictions boolean? true Auto-resolve contradictions (keeps higher-salience fact)
maxFactsPerPredicate int? null Diversity cap — max facts per predicate type
relevanceBuckets Bucket[]? null Per-domain weighting: name, predicates, weight (triggers optimization engine)

When goals, sessionId, or relevanceBuckets are present, the response includes additional fields: formattedText, windowId, rules, contradictionsFound, contradictionsResolved, contradictions, deduplicationSavings, bucketStats, goalDriven, knowledgeGeneration.

POST /memory/consolidate

Compress repetitive episodic patterns into semantic summaries.

CLI-friendly alias: POST /memory/compress.

POST /memory/decay

Evict expired and low-salience facts.

CLI-friendly alias: POST /memory/cleanup.

{ "threshold": 0.05 }

Context Management Engine

The context optimization API — the core of NocturnusAI's cost reduction story. These endpoints deliver goal-driven, salience-ranked, contradiction-checked context windows that cut token costs by 82–90% (measured on live Claude Opus 4 and Gemini 2.0 Flash).

This is the money saver. Instead of replaying the full conversation every turn (~1,259 tokens/turn on Claude Opus 4), these endpoints return only the ~221-token delta of goal-relevant facts. Same correct answer, 82% lower bill. See the benchmark →
Recommended production loop. Use /context for fast turn ingestion, /memory/context with goals and a stable sessionId for assembly, /context/diff for follow-up turns, and /context/session/clear when the conversation ends.
Method + Endpoint When to Use It
POST /memory/context Unified context endpoint — simple salience window OR goal-driven optimization (progressive disclosure)
POST /context Turns in, facts out (simple ingestion + selection)
POST /context/optimize Deprecated (sunset 2026-07-01) — use POST /memory/context with goals
POST /context/diff Delta updates since last optimized window
POST /context/summary Context/KB telemetry (counts, salience, contradictions)
POST /context/session/clear Delete diff snapshot state at end of conversation
POST /context/ingest One-shot extract + assert + optimize workflow

POST /context

The simplest way to use NocturnusAI. Pass an array of conversation turns (strings), get back relevant facts. NocturnusAI extracts, stores, deduplicates, and ranks automatically.

cURL

curl -X POST http://localhost:9300/context \
  -H "Content-Type: application/json" \
  -H "X-Tenant-ID: default" \
  -d '{
    "turns": [
      "Acme Corp is on the enterprise plan and based in Austin.",
      "They have a $2M contract with 24/7 SLA support."
    ],
    "maxFacts": 50
  }'

Request

{
  "turns": [
    "Acme Corp is on the enterprise plan and based in Austin.",
    "They have a $2M contract with 24/7 SLA support."
  ],
  "maxFacts": 50,
  "scope": null
}

Only turns is required. maxFacts defaults to 50. scope is optional for data isolation.

Response

{
  "facts": [
    { "predicate": "customer_tier", "args": ["acme_corp", "enterprise"], "salience": 0.95, "provenance": null },
    { "predicate": "location", "args": ["acme_corp", "austin"], "salience": 0.88, "provenance": null },
    { "predicate": "contract_value", "args": ["acme_corp", "2000000"], "salience": 0.92, "provenance": null },
    { "predicate": "sla_tier", "args": ["acme_corp", "24_7"], "salience": 0.90, "provenance": null }
  ],
  "totalFactsInKB": 127,
  "factsReturned": 4,
  "contradictions": 0,
  "newFactsExtracted": 4
}

Feed the facts array directly into your LLM's system prompt. See the Context Optimization guide for full integration examples with OpenAI, Claude, LangChain, and MCP.


POST /context/optimize (deprecated — sunset 2026-07-01)

Deprecated. Use POST /memory/context with the same parameters instead. This endpoint returns a Deprecation: true response header. It will be removed after 2026-07-01.

Build an optimized context window. Optionally goal-driven via backward chaining.

cURL

curl -X POST http://localhost:9300/context/optimize \
  -H "Content-Type: application/json" \
  -H "X-Tenant-ID: default" \
  -d '{
    "maxFacts": 25,
    "goals": [
      {"predicate":"enforceable","args":["contract_001"]}
    ],
    "sessionId": "session-42"
  }'

Request

{
  "maxFacts": 25,
  "goals": [
    { "predicate": "enforceable", "args": ["contract_001"] },
    { "predicate": "exception_applies", "args": ["contract_001", "?e"] }
  ],
  "relevanceBuckets": [
    { "name": "contract", "predicates": ["contract_type", "contract_value"], "weight": 1.5 },
    { "name": "legal", "predicates": ["enforceable", "statute_of_frauds"], "weight": 3.0 }
  ],
  "scope": null,
  "predicates": null,
  "sessionId": "session-42",
  "autoResolveContradictions": true,
  "maxFactsPerPredicate": null
}
Field Type Default Description
maxFacts int? 100 Maximum facts in the context window
goals GoalSpec[]? null Goal atoms for backward chaining. Each has predicate, args, optional negated
relevanceBuckets Bucket[]? null Per-domain weighting: name, predicates, weight
scope string? null Restrict to facts in this scope
predicates string[]? null Filter to specific predicate names
sessionId string? null Session ID for incremental diffs (stores snapshot)
autoResolveContradictions boolean true Auto-resolve contradictions (keeps higher-salience fact)
maxFactsPerPredicate int? null Diversity cap — max facts per predicate type

Response

{
  "windowId": "ctx-a7f3b2e1",
  "entries": [
    {
      "predicate": "enforceable",
      "args": ["contract_001"],
      "negated": false,
      "scope": null,
      "salience": 0.98,
      "category": "goal_relevant",
      "charCount": 28,
      "provenance": {
        "rule": "enforceable(?c) :- signed(?c), consideration(?c)",
        "premises": ["signed(contract_001)", "consideration(contract_001)"]
      },
      "createdAt": 1712180400000,
      "validFrom": null,
      "validUntil": null,
      "metadata": {}
    }
  ],
  "relevantRules": ["enforceable(?c) :- signed(?c), consideration(?c)"],
  "totalFactsAvailable": 512,
  "totalFactsIncluded": 15,
  "deduplicationSavings": 3,
  "contradictionsFound": 1,
  "contradictionsResolved": 1,
  "contradictions": [
    {
      "predicate": "valid_until",
      "args": ["contract_001", "2024-12"],
      "positiveSalience": 0.7,
      "negativeSalience": 0.3
    }
  ],
  "bucketStats": {
    "contract": { "factsIncluded": 6, "maxAllocation": 8, "minSalience": 0.45, "maxSalience": 0.98 },
    "legal": { "factsIncluded": 9, "maxAllocation": 10, "minSalience": 0.52, "maxSalience": 0.98 }
  },
  "totalCharCount": 820,
  "goalDriven": true,
  "knowledgeGeneration": 47,
  "generatedAt": 1712180450000
}

totalCharCount is useful for prompt budgeting. A quick estimate is tokens ≈ totalCharCount / 4.


POST /context/diff

Get incremental changes since a previous context window. Requires a sessionId that was used in a prior /memory/context call (with goals or sessionId). Only sends what changed — further reducing token spend on multi-turn conversations.

Request

{
  "sessionId": "session-42",
  "maxFacts": 25,
  "scope": null,
  "goals": [{ "predicate": "enforceable", "args": ["contract_001"] }]
}

Response

{
  "previousWindowId": "ctx-a7f3b2e1",
  "currentWindowId": "ctx-b8g4c3f2",
  "added": [
    { "predicate": "amendment", "args": ["contract_001", "clause_7"], "salience": 0.91, "category": "goal_relevant", "charCount": 38 }
  ],
  "removed": [
    { "key": "valid_until/contract_001/2024-12", "predicate": "valid_until", "args": ["contract_001", "2024-12"], "negated": false, "scope": null }
  ],
  "unchanged": 14,
  "fullRefreshRecommended": false,
  "reason": null
}

POST /context/summary

Get a compact summary of the knowledge base — predicate counts, expiring facts, contradictions, and top salient facts. Useful for dashboard views and monitoring.

Request

{ "scope": null }

Response

{
  "totalFacts": 512,
  "predicateCount": 23,
  "topPredicates": [
    { "predicate": "contract_type", "count": 87 },
    { "predicate": "customer_tier", "count": 64 }
  ],
  "factsWithTtl": 45,
  "factsExpiringWithin1h": 12,
  "contradictions": 3,
  "topSalientFacts": [],
  "totalCharCount": 24500,
  "knowledgeGeneration": 47,
  "generatedAt": 1712180450000
}

POST /context/session/clear

Clear session state used for context diffing. Call this when a conversation ends or to reset diff tracking.

Request

{ "sessionId": "session-42" }

Response

Session 'session-42' cleared

POST /context/ingest

One-shot pipeline: extract facts from text (via LLM or predicate syntax), assert them, and return an optimized context window — all in a single request. Requires LLM extraction to be enabled for natural language input.

Request

{
  "text": "Acme Corp signed contract #001 for $2M. The contract includes an arbitration clause.",
  "goals": [{ "predicate": "enforceable", "args": ["contract_001"] }],
  "maxFacts": 25,
  "scope": null,
  "sessionId": "session-42",
  "autoResolveContradictions": true,
  "contextHint": "Legal contract analysis"
}

Response

{
  "extracted": [
    { "predicate": "contract_signed", "args": ["acme_corp", "contract_001"], "confidence": 0.95 },
    { "predicate": "contract_value", "args": ["contract_001", "2000000"], "confidence": 0.92 },
    { "predicate": "has_clause", "args": ["contract_001", "arbitration"], "confidence": 0.88 }
  ],
  "extractionProvider": "anthropic",
  "context": {
    "windowId": "ctx-c9h5d4g3",
    "entries": [...],
    "totalFactsAvailable": 515,
    "totalFactsIncluded": 18,
    "totalCharCount": 940,
    "goalDriven": true
  }
}

Admin Endpoints

Endpoint Description
GET /health Health check (Kubernetes liveness)
GET /health/ready Readiness check
GET /predicates Discover knowledge base schema
GET /admin/databases List all databases
POST /admin/databases Create a new database
GET /metrics Prometheus metrics
POST /tx/begin Begin ACID transaction
POST /tx/commit/:id Commit transaction
POST /tx/rollback/:id Rollback transaction
GET /admin/databases/:name/tenants List tenants in a database
POST /admin/databases/:name/tenants Create a tenant
DELETE /admin/databases/:name/tenants/:id Delete a tenant and all its data
POST /admin/databases/:name/nuke Delete all data in a database
POST /admin/databases/:name/tenants/:id/nuke Delete all data in a tenant

Scope Management

Scopes are logical partitions within a tenant for hypothetical reasoning, versioning, and A/B testing. Use Git-like fork/diff/merge operations to branch knowledge.

Endpoint Description
POST /scope/fork Fork a scope — copies all facts from source to a new scope
POST /scope/diff Diff two scopes — shows added/removed/modified facts
POST /scope/merge Merge scopes with strategy: SOURCE_WINS, TARGET_WINS, KEEP_BOTH, REJECT
DELETE /scope/:name Delete a scope and all its facts
GET /scopes List all scopes in the current tenant

Aggregation & Bulk Operations

Endpoint Description
POST /aggregate Aggregation queries: COUNT, SUM, MIN, MAX, AVG on fact arguments
POST /assert/facts Bulk assert — submit an array of facts in a single request
POST /retract/pattern Pattern-based retraction — remove all facts matching a pattern

What's Next?