Status: implementation target
Base URL:
http://127.0.0.1:8766
All endpoints return JSON. Errors use a stable shape:
{
error: {
code: string;
message: string;
details?: object;
}
}GET /health
GET /catalog
GET /settings
GET /settings/schema
PATCH /settings
GET /sessions
POST /sessions
GET /sessions/{session_id}
PATCH /sessions/{session_id}GET /graph/overview
GET /materials/random
GET /materials/{material_id}
GET /materials/{material_id}/workspace
GET /materials/{material_id}/evidence
GET /materials/{material_id}/neighborhood
GET /edges/{edge_id}
GET /searchPOST /screen
POST /compare
POST /candidate-sets
GET /candidate-sets/{candidate_set_id}
PATCH /candidate-sets/{candidate_set_id}
POST /export/subgraph
POST /export/candidatesGET /agent/tools
POST /agent/chat
POST /agent/actions/{action_id}/confirmGET /research/status
POST /research/query
GET /research/runs/{run_id}
POST /research/ingest-url
POST /research/ingest-pdf
POST /research/candidates/{candidate_id}/promotePurpose: quick backend liveness.
Response:
{
status: "ok";
backend: "local-files-duckdb";
version: string;
}Purpose: UI and agent discover dataset capabilities without hardcoded counts.
Response:
{
product: "Catalyst";
source: {
name: "Materials Project";
source_release: string;
snapshot_label: string;
};
counts: {
materials: number;
elements: number;
material_element_edges: number;
material_material_edges: number;
evidence_rows: number;
overview_clusters: number;
curated_start_materials: number;
research_candidates: number;
};
capabilities: {
local_search: true;
graph_overview: true;
material_workspace: true;
candidate_compare: true;
export_json: true;
export_csv: true;
agent: boolean;
research_mode: boolean;
pdf_ingest: boolean;
url_ingest: boolean;
multimodal_inputs: boolean;
};
provider_status: {
llm_configured: boolean;
active_provider: string | null;
literature_sources: Record<string, "available" | "missing_key" | "disabled" | "not_configured">;
};
}Purpose: let the UI read local runtime settings and provider/source readiness.
Response:
{
settings: CatalystSettings;
provider_status: ProviderStatus;
research_sources: Record<string, "available" | "missing_key" | "disabled" | "not_configured">;
}Purpose: update non-secret local runtime settings under data/local/settings.json.
Request:
{
runtime?: Partial<RuntimeSettings>;
providers?: Partial<ProviderSettings>;
research?: Partial<ResearchSettings>;
sessions?: Partial<SessionSettings>;
}Response: same shape as GET /settings.
API keys are read from environment variables for V1. The backend reports key status but does not expose secret values.
Query:
{
limit_clusters?: number; // 10..1000, default 250
}Response remains compatible with docs/frontend-backend-contract.md.
Query:
{
mode?: "curated" | "any";
}Response: MaterialRecord.
Purpose: primary UI material payload.
Response:
{
material_id: string;
resolved_material_id: string;
namespace: "materials_project_snapshot" | "materials_project_target_cache" | "external_research";
material: MaterialRecord;
workspace_index: WorkspaceIndex;
summary: MaterialSummary;
structure: MaterialStructureSummary;
properties: {
thermo: object;
electronic: object;
magnetism: object;
mechanical: object;
dielectric?: object;
surfaces?: object;
};
evidence: EvidencePayload;
graph: NeighborhoodGraph;
relation_count: number;
actions: WorkspaceAction[];
}Query:
{
query?: string;
limit?: number;
elements?: string; // comma-separated, e.g. O,Fe
chemsys?: string; // e.g. Mn-O
stable?: boolean;
metal?: boolean;
magnetic?: boolean;
band_gap_min?: number;
band_gap_max?: number;
density_max?: number;
density_min?: number;
evidence?: string;
include_research?: boolean;
}Response:
{
query: string;
filters: object;
results: SearchResult[];
}Purpose: deterministic candidate screening from a natural-language material requirement. This can be called by the agent or UI directly.
Request:
{
requirement: string;
context?: {
session_id?: string;
current_material_id?: string;
candidate_set_id?: string;
};
options?: {
limit?: number;
include_research_candidates?: boolean;
strict_required_properties?: boolean;
};
}Response:
{
requirement: string;
parsed_requirements: ParsedRequirement[];
candidates: CandidateRanking[];
unsupported_requirements: UnsupportedRequirement[];
research_suggestion?: ResearchSuggestion;
}Request:
{
material_ids: string[];
include_evidence?: boolean;
include_edges?: boolean;
}Response:
{
materials: CandidateComparisonRow[];
columns: ComparisonColumn[];
evidence: Record<string, EvidencePayload>;
relation_summaries: RelationSummary[];
}Request:
{
material_ids: string[];
include_evidence?: boolean;
include_edge_details?: boolean;
format?: "json";
}Response:
{
export_id: string;
source_release: string;
requested_material_ids: string[];
nodes: NeighborhoodGraph["nodes"];
edges: NeighborhoodGraph["edges"];
evidence?: Record<string, EvidencePayload>;
edge_details?: Record<string, EdgeDetail>;
}Request:
{
candidate_set_id?: string;
material_ids?: string[];
format: "json" | "csv";
}Response:
{
export_id: string;
format: "json" | "csv";
path: string;
preview?: object;
}Request:
{
session_id: string;
message: string;
current_workspace?: {
material_id?: string;
selected_edge_id?: string;
candidate_set_id?: string;
visible_material_ids?: string[];
};
attachments?: AttachmentRef[];
stream?: boolean;
}Response:
{
session_id: string;
assistant_message: {
id: string;
text: string;
citations: CitationRef[];
confidence: "grounded" | "partial" | "research_required";
};
actions: AgentUIAction[];
candidate_results?: CandidateRanking[];
updated_context: SessionContextSummary;
}Streaming may be added, but the non-streaming response is the required V1 contract.
/agent/chat is LLM-first when a working provider is configured: the backend
compiles the current agent JSON/context into Markdown, sends it with the session
messages and tool declarations, executes requested tool calls, then asks the LLM
for the final answer. If no provider key is available or the provider call
fails, the endpoint falls back to the local deterministic controller so the UI
does not break.
Request:
{
session_id: string;
query: string;
context?: {
current_material_id?: string;
requirement?: string;
missing_properties?: string[];
};
sources?: Array<"openalex" | "semantic_scholar" | "arxiv" | "crossref" | "pubmed" | "web">;
}Response:
{
run_id: string;
status: "queued" | "running" | "completed" | "failed" | "disabled";
message: string;
}If research mode is disabled, return status: "disabled" with a clear
configuration message.
When research mode is enabled and at least one requested source is available,
the backend performs a lightweight source search, stores source hit records
under data/local/research_sources/, and the full run can be read from
GET /research/runs/{run_id}.