API Client & Endpoints

Production-ready API client layer for the MetaForge Digital Twin Dashboard. Each module exports typed functions that wrap Axios calls to the Gateway REST API.


src/api/client.ts

Shared Axios instance with interceptors for auth tokens and structured error handling.

import axios, {
  type AxiosError,
  type AxiosInstance,
  type AxiosResponse,
  type InternalAxiosRequestConfig,
} from "axios";

// ---------------------------------------------------------------------------
// Error types
// ---------------------------------------------------------------------------

export interface ApiErrorPayload {
  code: string;
  message: string;
  details?: Record<string, unknown>;
}

export class ApiError extends Error {
  readonly code: string;
  readonly status: number;
  readonly details?: Record<string, unknown>;

  constructor(status: number, payload: ApiErrorPayload) {
    super(payload.message);
    this.name = "ApiError";
    this.code = payload.code;
    this.status = status;
    this.details = payload.details;
  }
}

// ---------------------------------------------------------------------------
// Client factory
// ---------------------------------------------------------------------------

function createClient(): AxiosInstance {
  const instance = axios.create({
    baseURL: import.meta.env.VITE_API_URL ?? "/api/v1",
    headers: { "Content-Type": "application/json" },
    timeout: 30_000,
  });

  // ---- request interceptor: attach bearer token when present -------------
  instance.interceptors.request.use(
    (config: InternalAxiosRequestConfig) => {
      const token =
        typeof window !== "undefined"
          ? localStorage.getItem("metaforge_token")
          : null;

      if (token && config.headers) {
        config.headers.Authorization = `Bearer ${token}`;
      }
      return config;
    },
    (error: AxiosError) => Promise.reject(error),
  );

  // ---- response interceptor: normalise errors into ApiError --------------
  instance.interceptors.response.use(
    (response: AxiosResponse) => response,
    (error: AxiosError<{ error?: ApiErrorPayload }>) => {
      if (error.response) {
        const payload: ApiErrorPayload = error.response.data?.error ?? {
          code: "UNKNOWN_ERROR",
          message: error.message,
        };
        return Promise.reject(new ApiError(error.response.status, payload));
      }

      // Network / timeout errors
      return Promise.reject(
        new ApiError(0, {
          code: "NETWORK_ERROR",
          message: error.message ?? "Network request failed",
        }),
      );
    },
  );

  return instance;
}

export const client = createClient();

src/api/endpoints/sessions.ts

CRUD operations for orchestration sessions.

import { client } from "@/api/client";
import type {
  PaginatedResponse,
  Session,
  SessionStatus,
} from "@/types/session";

export interface GetSessionsParams {
  limit?: number;
  offset?: number;
  status?: SessionStatus;
}

export interface CreateSessionPayload {
  skill: string;
  files?: string[];
  args?: Record<string, unknown>;
}

/**
 * List sessions with optional filtering and pagination.
 */
export async function getSessions(
  params?: GetSessionsParams,
): Promise<PaginatedResponse<Session>> {
  const { data } = await client.get<PaginatedResponse<Session>>("/sessions", {
    params,
  });
  return data;
}

/**
 * Retrieve a single session by ID.
 */
export async function getSession(id: string): Promise<Session> {
  const { data } = await client.get<Session>(`/session/${id}`);
  return data;
}

/**
 * Create a new execution session.
 */
export async function createSession(
  payload: CreateSessionPayload,
): Promise<Session> {
  const { data } = await client.post<Session>("/session/create", {
    skill: payload.skill,
    input: {
      files: payload.files,
      args: payload.args,
    },
  });
  return data;
}

/**
 * Delete a session and its artifacts.
 */
export async function deleteSession(id: string): Promise<void> {
  await client.delete(`/session/${id}`);
}

src/api/endpoints/agents.ts

Agent execution and status polling.

import { client } from "@/api/client";
import type { Agent } from "@/types/agent";

export interface RunAgentPayload {
  agentId: string;
  sessionId: string;
}

export interface RunAgentResponse {
  runId: string;
}

/**
 * Retrieve all registered agents and their current state.
 * Derives from the `/status` endpoint's agent list.
 */
export async function getAgents(): Promise<Agent[]> {
  const { data } = await client.get<{ agents: Agent[] }>("/status");
  return data.agents;
}

/**
 * Get the current execution status for a specific agent.
 */
export async function getAgentStatus(id: string): Promise<Agent> {
  const { data } = await client.get<Agent>(`/agent/${id}/status`);
  return data;
}

/**
 * Trigger an agent run within an existing session.
 */
export async function runAgent(
  payload: RunAgentPayload,
): Promise<RunAgentResponse> {
  const { data } = await client.post<RunAgentResponse>("/agent/run", {
    agentId: payload.agentId,
    sessionId: payload.sessionId,
  });
  return data;
}

src/api/endpoints/approvals.ts

Human-in-the-loop approval workflow.

import { client } from "@/api/client";
import type { Approval } from "@/types/approval";

/**
 * Get all sessions waiting for human approval.
 */
export async function getPendingApprovals(): Promise<Approval[]> {
  const { data } = await client.get<{ pending: Approval[] }>("/pending");
  return data.pending;
}

/**
 * Approve a pending session. Optionally include a comment.
 */
export async function approveSession(
  sessionId: string,
  comment?: string,
): Promise<void> {
  await client.post(`/approve/${sessionId}`, comment ? { comment } : undefined);
}

/**
 * Reject a pending session. A reason is required.
 */
export async function rejectSession(
  sessionId: string,
  reason: string,
): Promise<void> {
  await client.post(`/reject/${sessionId}`, { reason });
}

src/api/endpoints/health.ts

System health and status probes.

import { client } from "@/api/client";
import type { HealthResponse, StatusResponse } from "@/types/system";

/**
 * Lightweight liveness probe.
 */
export async function getHealth(): Promise<HealthResponse> {
  const { data } = await client.get<HealthResponse>("/health");
  return data;
}

/**
 * Detailed system status including tool availability and session counts.
 */
export async function getStatus(): Promise<StatusResponse> {
  const { data } = await client.get<StatusResponse>("/status");
  return data;
}

src/api/endpoints/bom.ts

Bill of Materials retrieval and risk analysis.

import { client } from "@/api/client";
import type { BOMEntry, BOMRiskSummary } from "@/types/bom";

/**
 * Fetch the current Bill of Materials.
 */
export async function getBOM(): Promise<BOMEntry[]> {
  const { data } = await client.get<{ items: BOMEntry[] }>("/bom");
  return data.items;
}

/**
 * Fetch BOM risk analysis (single-source, EOL, lead-time flags).
 */
export async function getBOMRisk(): Promise<BOMRiskSummary> {
  const { data } = await client.get<BOMRiskSummary>("/bom/risk");
  return data;
}

src/api/endpoints/compliance.ts

Regulatory compliance summaries per target market.

import { client } from "@/api/client";
import type { ComplianceMarket, ComplianceSummary } from "@/types/compliance";

/**
 * Get compliance status for a specific market (UKCA, CE, or FCC).
 */
export async function getCompliance(
  market: ComplianceMarket,
): Promise<ComplianceSummary> {
  const { data } = await client.get<ComplianceSummary>(
    `/compliance/${market}`,
  );
  return data;
}

src/api/endpoints/digital-thread.ts

Digital-thread graph traversal and requirement traceability.

import { client } from "@/api/client";
import type { DigitalThreadGraph } from "@/types/digital-thread";

/**
 * Fetch the full digital-thread graph
 * (requirements <-> BOM <-> tests <-> artifacts).
 */
export async function getDigitalThread(): Promise<DigitalThreadGraph> {
  const { data } = await client.get<DigitalThreadGraph>("/digital-thread");
  return data;
}

/**
 * Trace a single requirement through the thread to see every linked
 * artefact, test, and BOM line.
 */
export async function getTraceability(
  reqId: string,
): Promise<DigitalThreadGraph> {
  const { data } = await client.get<DigitalThreadGraph>(
    `/digital-thread/trace/${reqId}`,
  );
  return data;
}

src/api/endpoints/supply-chain.ts

Supply-chain risk intelligence.

import { client } from "@/api/client";
import type { SupplyChainRisk } from "@/types/supply-chain";

/**
 * Retrieve current supply-chain risk entries
 * (lead-time alerts, geopolitical flags, single-source warnings).
 */
export async function getSupplyChainRisks(): Promise<SupplyChainRisk[]> {
  const { data } = await client.get<{ risks: SupplyChainRisk[] }>(
    "/supply-chain/risks",
  );
  return data.risks;
}

src/api/endpoints/testing.ts

Test-coverage metrics.

import { client } from "@/api/client";
import type { TestCoverage } from "@/types/testing";

/**
 * Fetch aggregate test-coverage data across all requirements.
 */
export async function getTestCoverage(): Promise<TestCoverage> {
  const { data } = await client.get<TestCoverage>("/testing/coverage");
  return data;
}

src/api/endpoints/artifacts.ts

Artifact retrieval and 3-D model download.

import { client } from "@/api/client";
import type { Artifact } from "@/types/artifact";

/**
 * Fetch metadata for a single artifact.
 */
export async function getArtifact(id: string): Promise<Artifact> {
  const { data } = await client.get<Artifact>(`/artifacts/${id}`);
  return data;
}

/**
 * Download the glTF binary for a 3-D artifact (digital-twin viewer).
 */
export async function getArtifactGltf(id: string): Promise<Blob> {
  const { data } = await client.get<Blob>(`/artifacts/${id}/gltf`, {
    responseType: "blob",
  });
  return data;
}

src/api/endpoints/gates.ts

Gate-readiness checks for hardware milestones.

import { client } from "@/api/client";
import type { GateReadiness, GateName } from "@/types/gate";

/**
 * Retrieve readiness status for a specific gate (EVT, DVT, or PVT).
 */
export async function getGateReadiness(
  gate: GateName,
): Promise<GateReadiness> {
  const { data } = await client.get<GateReadiness>(
    `/gate-readiness/${gate}`,
  );
  return data;
}

src/api/endpoints/index.ts

Barrel re-export for convenient imports.

export * from "./sessions";
export * from "./agents";
export * from "./approvals";
export * from "./health";
export * from "./bom";
export * from "./compliance";
export * from "./digital-thread";
export * from "./supply-chain";
export * from "./testing";
export * from "./artifacts";
export * from "./gates";
export * from "./chat";

src/api/endpoints/chat.ts (threads, messages, channels) is specified in Agent Chat Channel.