Web API Reference¶
Server: FastAPI at http://localhost:8088.
All endpoints (except GET /) require authentication. See Authentication below.
Authentication¶
Two modes controlled by the ZETTELFORGE_WEB_API_KEY environment variable:
- Loopback mode (no env var set, server on
127.0.0.1): no authentication required.localhost,::1, andtestclientare in the allowlist. - API key mode (env var set): pass the key via
X-API-Keyheader orAuthorization: Bearer <key>:
All endpoints also enforce rate limiting (60 requests per minute per key/IP by default). Exceeding the limit returns 429 Too Many Requests.
Existing Endpoints (preserved)¶
POST /api/recall¶
Blended vector + graph retrieval.
Request:
Response:
{
"query": "What tools does APT28 use?",
"results": [
{
"id": "zf-03f1a2b4",
"content": "APT28 has shifted tactics...",
"domain": "cti",
"tier": "B",
"confidence": 0.84,
"created_at": "2026-04-12T...",
"entities": ["APT28", "Cobalt Strike", "T1190"]
}
],
"count": 1,
"latency_ms": 47
}
POST /api/remember¶
Store a single note.
Request:
Response:
{"note_id": "zf-abc123", "status": "created", "entities": ["APT28", "Cobalt Strike"], "latency_ms": 45}
POST /api/synthesize¶
RAG synthesis from memory.
Request:
Formats: direct_answer, synthesized_brief, timeline_analysis, relationship_map.
GET /api/stats¶
Memory system statistics.
Response:
{
"version": "2.5.0",
"edition": "community",
"edition_name": "ZettelForge Community",
"total_notes": 4060,
"notes_created": 4060,
"retrievals": 1247,
"entity_index": {"apt28": ["actor"], "cve-2024-3094": ["cve"]}
}
GET /api/edition¶
Current edition and available feature flags. Enterprise features are false in Community edition.
POST /api/sync¶
OpenCTI sync (Enterprise only). Returns 501 in Community edition.
New Endpoints (RFC-015)¶
GET /api/health¶
System health information.
Response:
{
"version": "2.5.0",
"edition": "community",
"storage_backend": "sqlite",
"embedding_provider": "fastembed",
"embedding_model": "nomic-ai/nomic-embed-text-v1.5-Q",
"embedding_dimensions": 768,
"llm_provider": "ollama",
"llm_model": "qwen3.5:9b",
"llm_local_backend": "llama-cpp-python",
"enrichment_queue_depth": 0,
"governance_enabled": true,
"pii_enabled": false,
"uptime_seconds": 3600.5,
"data_dir": "~/.amem",
"memory_usage_mb": 245.3,
"data_size_mb": 25.0,
"total_notes": 4060,
"retrievals": 1247
}
Notes:
- memory_usage_mb is null if psutil is not installed (pip install zettelforge[web])
- data_size_mb is null if the data directory doesn't exist yet
GET /api/config¶
Full configuration as JSON with secrets redacted.
Response: All ZettelForgeConfig fields. Sensitive keys (api_key, password, token, secret, license_key) are replaced with "***".
Example excerpt:
{
"storage": {"data_dir": "~/.amem"},
"backend": "sqlite",
"embedding": {"provider": "fastembed", "model": "nomic-ai/nomic-embed-text-v1.5-Q", "dimensions": 768},
"llm": {"provider": "ollama", "api_key": "***", "local_backend": "llama-cpp-python"},
"governance": {"enabled": true, "pii": {"enabled": false}}
}
PUT /api/config¶
Apply configuration changes in-memory.
Request: Arbitrary nested config dict. Only fields that exist in the config dataclasses are applied.
Response:
{
"applied": ["retrieval.default_k", "synthesis.tier_filter"],
"pending_restart": [],
"message": "Configuration updated."
}
When the change requires a restart (e.g., backend, storage.data_dir, embedding.provider, llm.provider):
{
"applied": ["backend"],
"pending_restart": ["backend"],
"message": "Configuration updated. Some changes require a restart."
}
GET /api/graph/nodes¶
All knowledge graph entity nodes.
Response:
{
"nodes": [
{
"id": "apt28",
"label": "APT28",
"type": "actor",
"tier": "B",
"aliases": ["Fancy Bear", "Sofacy", "STRONTIUM"],
"confidence": 0.85,
"created_at": "2026-04-10"
}
],
"count": 47
}
GET /api/graph/edges¶
All knowledge graph relationship edges.
Response:
{
"edges": [
{
"id": "edge-001",
"source": "apt28",
"target": "cobalt_strike",
"relationship": "uses",
"created_at": "2026-04-10"
}
],
"count": 89
}
GET /api/entities¶
Paginated entity index with filters.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
offset |
int | 0 | Pagination offset |
limit |
int | 50 | Page size (max 200) |
type |
str | -- | Entity type filter (actor, cve, tool, campaign) |
tier |
str | -- | Tier filter (A, B, C) |
q |
str | -- | Text search across name and aliases |
Response:
{
"entities": [
{
"id": "apt28",
"name": "APT28",
"type": "actor",
"tier": "B",
"confidence": 0.85,
"aliases": ["Fancy Bear", "Sofacy"],
"first_seen": "2026-04-10",
"last_seen": null,
"connected_count": 3
}
],
"total": 47,
"offset": 0,
"limit": 50
}
GET /api/history¶
Recent activity from telemetry JSONL files.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int | 100 | Max entries (max 500) |
days |
int | 5 | Lookback window in days (max 30) |
Response: Array of telemetry entries, each containing:
{
"query_id": "a1b2c3d4",
"actor": null,
"query": "What tools does APT28 use?",
"event_type": "recall",
"timestamp": 1745612345.67,
"result_count": 3,
"duration_ms": 47,
"intent": "factual"
}
POST /api/ingest¶
Bulk ingestion for multiple notes.
Request:
{
"items": [
{"content": "APT28 used X-Agent.", "source_type": "report", "domain": "cti", "evolve": false},
{"content": "Lazarus targeted defense contractors.", "source_type": "report", "domain": "cti", "evolve": false}
]
}
Response:
{
"total": 2,
"succeeded": 2,
"failed": 0,
"results": [
{"note_id": "zf-abc123", "status": "created", "entities": ["APT28", "X-Agent"], "success": true},
{"note_id": "zf-def456", "status": "created", "entities": ["Lazarus"], "success": true}
]
}
Limits: Up to 100 items per request. Each item uses a write slot (max 2 concurrent writes).
GET /api/telemetry¶
Aggregated telemetry summary from today's data.
Response:
{
"total_queries": 142,
"recall_count": 114,
"synthesis_count": 28,
"avg_latency_ms": 47.3,
"p50_ms": 35,
"p95_ms": 210,
"top_intents": {"factual": 68, "temporal": 31, "causal": 24, "relational": 14, "exploratory": 5}
}
When no telemetry data exists yet for today:
{"total_queries": 0, "recall_count": 0, "synthesis_count": 0, "avg_latency_ms": null, "p50_ms": null, "p95_ms": null, "top_intents": {}}
GET /api/storage¶
Storage statistics.
Response:
GET /api/logs¶
Tail the structlog file with optional level filter.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
lines |
int | 100 | Number of lines to return (max 1000) |
level |
str | -- | Filter by level (DEBUG, INFO, WARNING, ERROR) |
Response:
{
"logs": [
{"event": "note_stored", "level": "INFO", "logger": "zettelforge.memory_manager", "note_id": "zf-abc", "timestamp": "..."}
],
"truncated": false
}
GET /api/logs/stream¶
Server-Sent Events stream for live log tailing. Polls the log file every 100ms for new lines.
data: {"event": "note_stored", "level": "INFO", ...}
data: {"event": "recall_completed", "level": "INFO", ...}
GET /api/telemetry/stream¶
Server-Sent Events stream for live telemetry. Polls today's telemetry JSONL file every 100ms.
data: {"event_type": "recall", "query_id": "abc", "duration_ms": 47, ...}
data: {"event_type": "synthesis", "query_id": "def", ...}
GET /¶
Serves the ZettelForge SPA from web/ui/index.html. If the UI directory is not found, returns a fallback HTML message.
Status Codes¶
| Code | Meaning |
|---|---|
200 |
Success |
400 |
Bad request (invalid parameters, unsupported synthesis format) |
401 |
Unauthorized (missing or invalid API key) |
429 |
Rate limit exceeded (60 req/min) or write capacity exhausted |
500 |
Internal server error (exception details in response) |
501 |
Feature requires zettelforge-enterprise (OpenCTI sync) |
503 |
API key required (server bound to non-loopback address without key) |