API Reference¶
This document is a task-oriented map of the API surface. It is intentionally not a generated endpoint inventory.
Source Of Truth¶
Use these sources in this order:
- Running OpenAPI UI at
http://localhost:8001/docs - Route registry in
src/web/routes/__init__.py - The individual route modules under
src/web/routes/
If this document disagrees with code or OpenAPI, trust the runtime.
Core Endpoint Groups¶
Health¶
GET /healthGET /api/healthGET /api/health/databaseGET /api/health/servicesGET /api/health/celery
Use these first when verifying the stack.
Sources And Ingestion¶
GET /api/sourcesGET /api/sources/{source_id}POST /api/sources/{source_id}/collectPUT /api/sources/{source_id}/image_ocr— Set the per-source OCR override. Body:{ "image_ocr_enabled": true | false | null }.trueforces OCR on regardless of the globalOCR_INGEST_ENABLEDenv;falseforces it off;nullclears the override and inherits the global default. Protected sources (eval/manual) reject this call with HTTP 400.POST /api/scrape-url
These endpoints control source state and manual collection.
Articles¶
GET /api/articlesGET /api/articles/{article_id}GET /api/articles/{article_id}/similarDELETE /api/articles/{article_id}GET /api/articles/{article_id}/chunk-debug— Chunk-level breakdown of the junk filter's keep/remove decisions, powering the Junk Filter Tuning modal. Local sklearn inference only; no LLM provider calls.GET /api/articles/{article_id}/chunk-debug/progress— Progress snapshot for an in-flightchunk-debugrun. Returnsin_progress: falsewhen nothing is running; otherwisephase,processed_chunks,total_chunks,article_total_chunksandchunk_limit_applied.phaseisfiltering(full-article pass, no per-chunk counter yet) →analyzing(processed_chunksadvancing towardtotal_chunks) →finalizing(assembling the response); onlyanalyzingcounts chunks, so a client must not render a counter during the other two.total_chunksis what this pass will analyse,article_total_chunksis what the article holds — they differ wheneverchunk_limit_appliedis true. Progress lives in Redis, not process memory, because a poll can land on a different uvicorn worker than the analysis.
These are the main article browsing and maintenance endpoints.
Article AI Endpoints¶
POST /api/articles/{article_id}/detect-os— Detect operating system from article content using deterministic entity/keyword-registry classification (no embedding model). Applies the content filter first. Returns HTTP 422 with{ "error": "no_huntable_content" }when the content filter finds no huntable chunks above the confidence threshold.POST /api/articles/{article_id}/rank-with-gpt4o— Rank article huntability using the active workflow config's RankAgent prompt and model.
Route module: src/web/routes/ai.py.
Search¶
POST /api/search/semanticGET /api/search/help
These power the semantic search workflow. For conversational retrieval, use the Huntable MCP server (see MCP tools reference).
Embeddings And Vector Coverage¶
GET /api/embeddings/stats— Embedding coverage summary. Response includes asigma_corpusblock (SigmaHQsigma_rulesrow counts vs rows with embeddings), distinct from the AI sigma_rule_queue. Used by CLIembed statsand MCPget_stats.
Workflow Execution¶
GET /api/workflow/executions— List executions with pagination. Query params:page(default 1),limit(default 50, max 200),status,step,article_id,sort_by,sort_order. Response:executions,total,page,total_pages,limit,running,completed,failed,pending.GET /api/workflow/executions/{execution_id}POST /api/workflow/articles/{article_id}/trigger— Query:force(bool, default false). Whentrue, skips the RegexHunt auto-trigger threshold; use for explicit manual runs. Ingestion auto-trigger still uses the threshold.POST /api/workflow/executions/{execution_id}/retryPOST /api/workflow/executions/{execution_id}/cancelPOST /api/workflow/executions/cleanup-stalePOST /api/workflow/executions/trigger-stuck— Re-dispatches stuckpendingexecutions onto the Celery workflow queue (onetrigger_agentic_workflowtask per row). Use it when a Celery task was lost — worker down at dispatch time, broker restart. Only rows pending longer thanSTUCK_PENDING_AFTER(5 min) and never started qualify; fresher rows are reported underskippedbecause their task is probably still queued. Eval rows (eval_runin the execution snapshot) get a longer window,eval_pending_stuck_after(): the 5-minute base plus the latest countdown the eval launcher can schedule (MAX_EVAL_EXECUTIONS_PER_LAUNCH− 1 steps ofEVAL_STAGGER_SECONDS+ the 60 s throttle ceiling, about 1 h 44 min at the default cap of 100), because a staggered launch commits every row up front and leaves its tail pending with live tasks. Double runs are prevented in the worker, not here:run_workflowclaims its row with a conditionalUPDATE … WHERE status = 'pending'and a duplicate dispatch for an already-claimed row logs a skip and returns without invoking the graph. The age filter keeps the endpoint from queueing noise and from counting such no-op dispatches assuccessful;count/successful/faileddescribe tasks queued, not runs performed.is_stuck_pending_executioninworkflow_trigger_service.pyis the single shared definition — the article-trigger path fails these same rows before starting a fresh execution, and blocks the trigger ("already has an active workflow execution") for a row still inside its window. It queues work; it does not run the LangGraph pipeline in the web process, which cannot importlanggraph.
The workflow engine writes its state into agentic_workflow_executions and exposes it through these endpoints.
Workflow Configuration¶
GET /api/workflow/config-
PUT /api/workflow/config— Rejects a config whose enabled extractors have no prompt (HTTP 400), because such a config produces runs that complete with zero observables and no error. Sendallow_prompt_warnings: trueto override deliberately ("Save Anyway"). Settings-only autosaves and disabled agents are exempt. Seesrc/web/routes/workflow_config.py.Also rejects (HTTP 400, no override) an agent whose provider is paired with a model the catalog attributes to a different provider — for example
lmstudiowithgpt-5.6-sol. Such a pair fails later at call time with a confusing "model not found" from the wrong provider.openaiandcodexshare one model namespace, socodex+gpt-5.6-*is valid. Only pairs this request changes are checked, so a config carrying a pre-existing mismatch stays saveable and can be repaired. Models absent fromconfig/provider_model_catalog.json(LMStudio local models, newly released models) are never rejected. -GET /api/workflow/config/prompts-GET /api/workflow/config/prompts/{agent_name}-GET /api/workflow/config/prompts/defaults/{agent_name}-- on-disk user template the runtime falls back to (SigmaAgent:sigma_generate_multi.txt) plus the code-default system prompt; used by the effective-prompt preview -PUT /api/workflow/config/prompts-GET /api/workflow/config/prompts/{agent_name}/versions-GET /api/workflow/config/prompts/{agent_name}/by-config-version/{config_version}-POST /api/workflow/config/prompts/{agent_name}/rollback-POST /api/workflow/config/prompts/bootstrap-POST /api/workflow/config/prompts/reset-to-defaults— Reset individual agent prompts to seed defaults. Unlikebootstrap(which wholesale replaces all prompts), this selectively resets only the requested agents while preserving others. Body:{ "agent_names": ["RankAgent", ...] }(omitagent_namesto reset all). Seesrc/web/routes/workflow_config.py. -GET /api/workflow/config/versions— List config versions with pagination. Query params:page(default 1),limit(default 20, max 100),version(optional, exact integer match). Response:versions,total,page,total_pages. -GET /api/workflow/config/preset/list-POST /api/workflow/config/preset/save-GET /api/workflow/provider-options— Server-owned availability and model list for OpenAI, Anthropic, LM Studio, and the optional Codex subscription provider. Each provider block also carriesmodel_capabilities(model id →supports_temperature,supports_top_p,effort_levels,default_effort,source), read fromconfig/model_capabilities.jsonfor OpenAI/Anthropic and discovered live frommodel/listfor Codex (which addseffort_descriptions). The Agents page uses it to disable unsupported Temperature/Top_P sliders and populate the per-agent Effort select;PUT /api/workflow/configrejects an{Agent}_effortoutside the resolved model's tiers with HTTP 400. -PATCH /api/workflow/config/auto-trigger-threshold— Update the auto-trigger hunt score threshold (0–100). Body:{ "auto_trigger_hunt_score_threshold": <float> }. This is the only endpoint that changes this value. It mutates the active config row in-place and is intentionally excluded from the mainPUT /api/workflow/configendpoint and from all preset import/export paths. Manage this setting only through the Settings UI.
The v2 configuration contract's prompt-bearing agent names are RankAgent, SigmaAgent, CmdlineExtract, ProcTreeExtract, HuntQueriesExtract, RegistryExtract, ServicesExtract, ScheduledTasksExtract, and NetworkIndicatorExtract. ExtractAgent supplies model/provider fallback configuration but is not prompt-bearing. The legacy prompt endpoints do not enforce that schema allowlist and may expose established auxiliary database keys such as OSDetectionAgent or SigmaRepair; clients should not invent new names. QA agents (RankAgentQA and all extractor QA agents) were fully removed in v7.2.0 (commit b9645305, 2026-05-22; released 2026-05-29) and are no longer valid agent names.
Each prompt object is a JSON dict with these fields:
| Field | Type | Purpose |
|---|---|---|
role (or system) |
string | Agent persona — identity and expertise statement |
task (or objective) |
string | What the agent does this run |
instructions |
string | Rules, constraints, output format, JSON enforcement |
json_example |
string or dict | Concrete output schema example |
output_format |
dict | Legacy schema description (used if json_example absent) |
The user message scaffold (Title/URL/Content headers, instructions footer) is assembled by the runtime and is not part of the prompt config. See Prompt Architecture for how prompts are assembled at runtime.
The strict configuration contract is defined in src/config/workflow_config_schema.py.
Settings And Integrations¶
GET /api/settings/*— Reads never return a stored credential. For a key matching_is_sensitive_settingthe response carriesvalue: nullplusconfiguredand a shorthint; non-sensitive settings round-trip their value as before. Both read routes sendCache-Control: no-store.POST /api/settings/codex/test— Verify the deployment-managed Codex login without invoking a workflow model.POST /api/settings/github/test— Verify the stored GitHub PAT against the configured repository, server-side. Accepts optionaltoken/repooverrides so a freshly typed credential can be checked before it is saved.POST /api/test-openai-keyPOST /api/test-anthropic-keyPOST /api/test-lmstudio-connection
These endpoints manage runtime settings and provider connectivity. The provider key tests
accept an empty api_key and fall back to the stored credential, since the Settings page
cannot read one back to resend it.
Models And MLOps¶
GET /api/model/retrain-status— Poll retraining progress (idle / starting / loading / complete / error)POST /api/model/retrain— Trigger model retraining from user feedback and annotations. The script trains to a staging path first; a quality gate (recall_huntable ≥ 0.30 and f1_huntable ≥ 0.30) must pass before the staged model is promoted to the live path. If the gate fails, the status file is set toerrorwith messageRETRAIN REJECTEDand the live model is left untouched.GET /api/model/versions— List model versions with metrics. Query params:page(optional; omit for unpaginated),limit(default 10, max 100),version(exact version number search)POST /api/model/evaluate— Run evaluation of the current model on the annotated test setGET /api/model/eval-chunk-count— Count of chunks in the evaluation datasetGET /api/feedback/chunk-classification/{article_id}— All stored chunk feedback for an article, keyed by chunk id (newest per chunk). One request replaces the per-chunk lookups the Junk Filter Tuning modal used to issue while rendering.GET /api/feedback/chunk-classification/{article_id}/{chunk_id}— Feedback for a single chunk, orfeedback: null. Used to re-read one chunk after a write.POST /api/feedback/chunk-classification— Record user feedback on a chunk classification (writes into the ML training corpus).GET /api/model/feedback-count— Count of available feedback and annotation samples for retrainingPOST /api/model/rollback/{version_id}— Roll back to a specific model version. Copies the saved artifact to the live path, flipsis_current, clears the ContentFilter cache, and starts a background chunk re-scoring backfillGET /api/model/compare/{version_id}— Get or generate comparison results between a version and its predecessorGET /api/model/feedback-comparison— Before/after confidence levels for chunks that received user feedbackGET /api/model/classification-timeline— Classification breakdown across model versions for time series charting
Route module: src/web/routes/models.py. Version data is stored in the ml_model_versions table (see src/database/models.py).
AI / Inline LLM Endpoints¶
POST /api/articles/{article_id}/detect-os— Run OS detection on a single article. Returns OS label, confidence, and method. HTTP 422 when content filter finds no huntable chunks ({ "error": "no_huntable_content" }); client should checkis_huntablebefore calling if this matters.
Sigma Queue And Evaluation¶
GET /workflow/queue— HTML page for the Sigma queue console (redirects to/workflow#queue; uses/api/sigma-queue/*for data).GET /api/sigma-queue/list— List queued Sigma rules with pagination. Query params:status(optional, values:pending,needs_review,approved,rejected,submitted),workflow_execution_id(optional, filter to one workflow job's rules),limit(default 50, max 500),offset(default 0). Response:{ "items": [...], "total": N, "limit": L, "offset": O }.POST /api/sigma-queue/{queue_id}/enrich— Enrich a queued rule with OpenAI, Anthropic, LM Studio, or the optional Codex subscription provider. The configured provider credential stays server-side. A successful response includes the enriched YAML andconversation_idfor a persisted, rule-scoped follow-up conversation.GET /api/sigma-queue/{queue_id}/enrichment-chat— Return the latest persisted enrichment conversation for the rule. System prompts are excluded from the browser response.POST /api/sigma-queue/{queue_id}/enrichment-chat/{conversation_id}/turn— Continue that conversation through its original provider and model. Follow-up turns are limited to revising the current Sigma rule or discussing earlier enrichment turns.POST /api/sigma-queue/{queue_id}/validate— Validate and optionally LLM-enrich a queued rule. Returns{ "validated_yaml": ... }.GET /api/sigma-queue/*(other endpoints)GET /api/eval/os-detection-manual-resultsandGET /api/eval/observables-count-results— remaining legacy evaluation result routes (src/web/routes/evaluation.py)/mlops/agent-evalsand/mlops/agent-evals2— HTML evaluation pages registered insrc/web/routes/pages.py
Subagent Evaluation Endpoints¶
These support per-subagent extraction evals (CmdlineExtract, ProcTreeExtract, HuntQueriesExtract, RegistryExtract, ServicesExtract, ScheduledTasksExtract, NetworkIndicatorExtract):
GET /api/evaluations/subagent-eval-articles— List seeded eval articles for a given subagent.POST /api/evaluations/run-subagent-eval— Trigger a subagent eval run against the active config (the legacyuse_active_configfield is ignored). Rejected with 422 before any write when a URL has no committed fixture or the run exceedsMAX_EVAL_EXECUTIONS_PER_LAUNCH(default 100). Shares its planner with therun_subagent_evalMCP tool.GET /api/evaluations/subagent-eval-results— Get results for completed subagent eval runs (includesexpected_items,actual_items,matched_count,missed_count,extra_countwhen item-level ground truth is set).GET /api/evaluations/subagent-eval-status/{eval_record_id}— Poll status of a single eval record.DELETE /api/evaluations/subagent-eval-clear-pending— Clear pending/stuck eval records.POST /api/evaluations/subagent-eval-backfill— Backfill pending eval records from existing workflow executions.POST /api/evaluations/subagent-eval-rescore— Repair completed count-only records that have item-level ground truth but no score, using each record's retained extractor output (no paid LLM).?subagent=<name|all>;?apply=false(default) is a dry-run reportingcandidates/scorable/unrepairable_no_outputper agent,?apply=truepersists. Scores against the ground truth stored on the record (no disk reload) and is idempotent.GET /api/evaluations/subagent-eval-aggregate— Aggregated metrics perconfig_version. Includes count-based fields (mean_score,raw_mae,score_distribution) and item-level fields (mean_precision,mean_recall,mean_f1,scored_articles). Top-leveleval_set_totalreturns the canonical eval-article count fromconfig/eval_articles.yaml(used by the MAE chart to flag subset runs in amber). Optional?model=query param filters to versions where the subagent used the given model (powers the SYS.04 trend chart).GET /api/evaluations/subagent-eval-models?subagent=...— List models that have eval data for the given subagent, sorted by usage frequency. Powers the model dropdown on the/mlops/agent-evals2SYS.04 chart.GET /api/evaluations/config-versions-models— List config versions with model info for each agent.
Route module: src/web/routes/evaluation_api.py.
Finding The Right Route Module¶
Start in src/web/routes/__init__.py, then open the matching module:
workflow_executions.pyworkflow_config.pyarticles.pysources.pysettings.pysigma_queue.pymodels.pyevaluation_api.py— subagent eval runs, export bundles, config-version model lookup
Verification Guidance¶
- API behavior changes: run
python3 run_tests.py api - Workflow API changes: run
python3 run_tests.py integration - UI flows that call the API: run
python3 run_tests.py uiorpython3 run_tests.py e2e
Last updated: 2026-08-13 Last reviewed: 2026-09-01