Download OpenAPI specification:Download
HTTP/WebSocket front door for the MetaForge platform
Every approval, normalized. status=decided and all are the audit views.
| status | string (Status) Default: "pending" Enum: "pending" "decided" "all" |
Project Id (string) or Project Id (null) (Project Id) | |
Kind (string) or Kind (null) (Kind) |
{- "items": [
- {
- "allowed_decisions": [
- "approve"
], - "created_at": "string",
- "deadline": "string",
- "decidable": false,
- "decision": {
- "agent": "string",
- "approver": "string",
- "approver_verified": false,
- "decided_at": "string",
- "decision": "string",
- "on_behalf_of": "string",
- "reason": "",
- "surface": "dashboard"
}, - "detail": { },
- "findings": [
- {
- "kind": "missing_deliverable",
- "message": "string",
- "severity": "error"
}
], - "id": "string",
- "kind": "gate",
- "not_decidable_reason": "string",
- "project_id": "string",
- "reason_held": "string",
- "reason_required_for": [
- "string"
], - "requested_by": "string",
- "rework_targets": [
- "string"
], - "route": "dashboard",
- "status": "pending",
- "summary": "",
- "title": "string"
}
], - "unscoped_count": 0
}{- "allowed_decisions": [
- "approve"
], - "created_at": "string",
- "deadline": "string",
- "decidable": false,
- "decision": {
- "agent": "string",
- "approver": "string",
- "approver_verified": false,
- "decided_at": "string",
- "decision": "string",
- "on_behalf_of": "string",
- "reason": "",
- "surface": "dashboard"
}, - "detail": { },
- "findings": [
- {
- "kind": "missing_deliverable",
- "message": "string",
- "severity": "error"
}
], - "id": "string",
- "kind": "gate",
- "not_decidable_reason": "string",
- "project_id": "string",
- "reason_held": "string",
- "reason_required_for": [
- "string"
], - "requested_by": "string",
- "rework_targets": [
- "string"
], - "route": "dashboard",
- "status": "pending",
- "summary": "",
- "title": "string"
}Decide an approval of any kind.
The deciding human is the authenticated principal for every kind. A
reviewer or approved_by in the body is ignored and logged.
| approval_id required | string (Approval Id) |
| decision required | string (Decision) Enum: "approve" "reject" "retry" "rework" |
| reason | string (Reason) <= 2000 characters Default: "" |
| to_phase | string (To Phase) <= 200 characters Default: "" |
| property name* additional property | any |
{- "decision": "approve",
- "reason": "",
- "to_phase": ""
}{- "allowed_decisions": [
- "approve"
], - "created_at": "string",
- "deadline": "string",
- "decidable": false,
- "decision": {
- "agent": "string",
- "approver": "string",
- "approver_verified": false,
- "decided_at": "string",
- "decision": "string",
- "on_behalf_of": "string",
- "reason": "",
- "surface": "dashboard"
}, - "detail": { },
- "findings": [
- {
- "kind": "missing_deliverable",
- "message": "string",
- "severity": "error"
}
], - "id": "string",
- "kind": "gate",
- "not_decidable_reason": "string",
- "project_id": "string",
- "reason_held": "string",
- "reason_required_for": [
- "string"
], - "requested_by": "string",
- "rework_targets": [
- "string"
], - "route": "dashboard",
- "status": "pending",
- "summary": "",
- "title": "string"
}Record a gate decision a person gave in the MCP client's chat (FORGE-582).
Gates only: a held tool call's inline answer has its own route
(/v1/tool-approvals/{id}/resolve). The decision goes through the same
service.decide as a dashboard click, so a gate that is not ready
still cannot be approved, and retry and rework caps still apply.
| approval_id required | string (Approval Id) |
Approver (string) or Approver (null) (Approver) | |
| approver_verified | boolean (Approver Verified) Default: false |
| decision required | string (Decision) Enum: "approve" "reject" "retry" "rework" |
| reason | string (Reason) <= 2000 characters Default: "" |
| to_phase | string (To Phase) <= 200 characters Default: "" |
{- "approver": "string",
- "approver_verified": false,
- "decision": "approve",
- "reason": "",
- "to_phase": ""
}{- "allowed_decisions": [
- "approve"
], - "created_at": "string",
- "deadline": "string",
- "decidable": false,
- "decision": {
- "agent": "string",
- "approver": "string",
- "approver_verified": false,
- "decided_at": "string",
- "decision": "string",
- "on_behalf_of": "string",
- "reason": "",
- "surface": "dashboard"
}, - "detail": { },
- "findings": [
- {
- "kind": "missing_deliverable",
- "message": "string",
- "severity": "error"
}
], - "id": "string",
- "kind": "gate",
- "not_decidable_reason": "string",
- "project_id": "string",
- "reason_held": "string",
- "reason_required_for": [
- "string"
], - "requested_by": "string",
- "rework_targets": [
- "string"
], - "route": "dashboard",
- "status": "pending",
- "summary": "",
- "title": "string"
}List pending design-change proposals, optionally filtered by session/project.
Session Id (string) or Session Id (null) (Session Id) | |
Project Id (string) or Project Id (null) (Project Id) |
{- "proposals": [
- {
- "agent_code": "string",
- "change_id": "53a22efb-209d-4639-a6e7-7072a755c213",
- "created_at": "2019-08-24T14:15:22Z",
- "decided_at": "2019-08-24T14:15:22Z",
- "decision_agent": "string",
- "decision_on_behalf_of": "string",
- "decision_reason": "string",
- "decision_surface": "string",
- "description": "string",
- "diff": { },
- "project_id": "string",
- "requires_approval": true,
- "reviewer": "string",
- "reviewer_verified": false,
- "session_id": "1ffd059c-17ea-40a8-8aef-70fd0307db82",
- "status": "pending",
- "work_products_affected": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
], - "total": 0
}Create a design-change proposal directly from a human (MET-630).
Same underlying ApprovalWorkflow.propose_change an agent's
twin.propose_change MCP call uses — a human (e.g. via a dashboard
parameter panel) can submit one just as well, and it goes through the
identical review/apply pipeline (decide → apply executor).
| agent_code | string (Agent Code) non-empty Default: "human" Code identifying who/what is proposing the change |
| description required | string (Description) non-empty What the change does |
object (Diff) Structured diff (e.g. {'action': 'regenerate_geometry'}) | |
Project Id (string) or Project Id (null) (Project Id) | |
Session Id (string) or Session Id (null) (Session Id) | |
| work_products_affected | Array of strings <uuid> (Work Products Affected) [ items <uuid > ] |
{- "agent_code": "human",
- "description": "string",
- "diff": { },
- "project_id": "string",
- "session_id": "1ffd059c-17ea-40a8-8aef-70fd0307db82",
- "work_products_affected": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}{- "agent_code": "string",
- "change_id": "53a22efb-209d-4639-a6e7-7072a755c213",
- "created_at": "2019-08-24T14:15:22Z",
- "decided_at": "2019-08-24T14:15:22Z",
- "decision_agent": "string",
- "decision_on_behalf_of": "string",
- "decision_reason": "string",
- "decision_surface": "string",
- "description": "string",
- "diff": { },
- "project_id": "string",
- "requires_approval": true,
- "reviewer": "string",
- "reviewer_verified": false,
- "session_id": "1ffd059c-17ea-40a8-8aef-70fd0307db82",
- "status": "pending",
- "work_products_affected": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}Return a single design-change proposal.
| change_id required | string <uuid> (Change Id) |
{- "agent_code": "string",
- "change_id": "53a22efb-209d-4639-a6e7-7072a755c213",
- "created_at": "2019-08-24T14:15:22Z",
- "decided_at": "2019-08-24T14:15:22Z",
- "decision_agent": "string",
- "decision_on_behalf_of": "string",
- "decision_reason": "string",
- "decision_surface": "string",
- "description": "string",
- "diff": { },
- "project_id": "string",
- "requires_approval": true,
- "reviewer": "string",
- "reviewer_verified": false,
- "session_id": "1ffd059c-17ea-40a8-8aef-70fd0307db82",
- "status": "pending",
- "work_products_affected": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}Approve or reject a pending design-change proposal.
On approval, run the proposal's diff via the apply executor (if wired) so the change is actually applied to the twin (HITL: propose → approve → apply).
| change_id required | string <uuid> (Change Id) |
| change_id required | string <uuid> (Change Id) UUID of the proposal to decide on |
| decision required | string (ApprovalDecisionType) Enum: "approve" "reject" Approve or reject |
| reason required | string (Reason) non-empty Human-written justification for the decision |
| reviewer required | string (Reviewer) non-empty Identifier of the human reviewer |
{- "change_id": "53a22efb-209d-4639-a6e7-7072a755c213",
- "decision": "approve",
- "reason": "string",
- "reviewer": "string"
}{- "agent_code": "string",
- "change_id": "53a22efb-209d-4639-a6e7-7072a755c213",
- "created_at": "2019-08-24T14:15:22Z",
- "decided_at": "2019-08-24T14:15:22Z",
- "decision_agent": "string",
- "decision_on_behalf_of": "string",
- "decision_reason": "string",
- "decision_surface": "string",
- "description": "string",
- "diff": { },
- "project_id": "string",
- "requires_approval": true,
- "reviewer": "string",
- "reviewer_verified": false,
- "session_id": "1ffd059c-17ea-40a8-8aef-70fd0307db82",
- "status": "pending",
- "work_products_affected": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}Submit a request to an agent via the orchestrator.
Looks up the workflow definition for body.action, creates a
WorkflowRun, and dispatches it through the Scheduler.
| action required | string (Action) non-empty Agent action to perform (e.g. 'validate_stress', 'run_drc') |
object (Parameters) Action-specific parameters | |
| project_id required | string (Project Id) non-empty Project this request belongs to |
| prompt | string (Prompt) Default: "" Free-text prompt or description for generative actions (e.g. generate_cad) |
| session_id | string <uuid> (Session Id) Session ID for grouping related requests |
Target Id (string) or Target Id (null) (Target Id) UUID of the target work_product (optional for generative actions) |
{- "action": "string",
- "parameters": { },
- "project_id": "string",
- "prompt": "",
- "session_id": "1ffd059c-17ea-40a8-8aef-70fd0307db82",
- "target_id": "d3bcdc92-4191-401b-ad0c-42056c6efab9"
}{- "errors": [
- "string"
], - "request_id": "266ea41d-adf5-480b-af50-15b940c2b846",
- "result": { },
- "status": "string"
}List BOM components, optionally scoped to a project and/or filtered
by category (case-insensitive exact match against
BOMItem.specifications.category, e.g. category=fastener for a
fastener list -- FORGE-294, gap G-H2).
Empty ({components: [], total: 0}) when the project has no BOM — not a
404, so the dashboard renders a clean empty state.
Project Id (string) or Project Id (null) (Project Id) | |
Category (string) or Category (null) (Category) |
{- "components": [
- {
- "cadModelUrl": "string",
- "category": "string",
- "datasheetUrl": "string",
- "description": "string",
- "designator": "string",
- "footprint": "string",
- "id": "string",
- "imageUrl": "string",
- "manufacturer": "string",
- "partNumber": "string",
- "priceCurrency": "string",
- "projectId": "string",
- "purchaseUrl": "string",
- "quantity": 0,
- "status": "available",
- "unitPrice": 0
}
], - "total": 0
}Derive the hierarchical BOM (EBOM) -- FORGE-267, gap G-C3 -- from every "product"-kind HierarchyNode's CONTAINS tree in a project.
Empty (not an error) when the project has no product hierarchy yet (FORGE-260's tools haven't been used for it) -- matches the flat BOM's own convention.
Project Id (string) or Project Id (null) (Project Id) |
{- "lines": [
- {
- "componentId": "string",
- "description": "string",
- "hierarchyNodeId": "string",
- "manufacturer": "string",
- "partNumber": "string",
- "path": [
- "string"
], - "quantity": 0,
- "source": "string",
- "unitCost": 0
}
], - "total": 0
}{- "entries": [
- {
- "created_at": "string",
- "node_id": "string",
- "statement": "string",
- "steps": [
- {
- "base": "string",
- "follower": "string",
- "instruction": "string",
- "joint_name": "string",
- "joint_type": "string",
- "step_number": 0
}
], - "title": "string"
}
]
}Project Id (string) or Project Id (null) (Project Id) | |
| work_product_id required | string (Work Product Id) |
{- "project_id": "string",
- "work_product_id": "string"
}{- "node_id": "string",
- "statement": "string",
- "steps": [
- {
- "base": "string",
- "follower": "string",
- "instruction": "string",
- "joint_name": "string",
- "joint_type": "string",
- "step_number": 0
}
]
}| default_urdf_path required | string (Default Urdf Path) non-empty Default 'urdf_path' launch argument — e.g. a just-exported URDF's download_url. |
| include_joint_state_publisher_gui | boolean (Include Joint State Publisher Gui) Default: true |
| include_rviz | boolean (Include Rviz) Default: true |
| robot_name required | string (Robot Name) non-empty |
{- "default_urdf_path": "string",
- "include_joint_state_publisher_gui": true,
- "include_rviz": true,
- "robot_name": "string"
}{- "default_urdf_path": "string",
- "output_file": {
- "download_url": "string",
- "filename": "string"
}, - "robot_name": "string"
}Density Kg M3 (number) or Density Kg M3 (null) (Density Kg M3) | |
| link_name | string (Link Name) Default: "link" |
Material (string) or Material (null) (Material) | |
| mesh_format | string (Mesh Format) Default: "stl" Enum: "stl" "obj" |
| model_name | string (Model Name) Default: "model" |
| node_id required | string (Node Id) non-empty Twin work-product node id (STEP file). |
| static | boolean (Static) Default: false |
World Name (string) or World Name (null) (World Name) |
{- "density_kg_m3": 0,
- "link_name": "link",
- "material": "string",
- "mesh_format": "stl",
- "model_name": "model",
- "node_id": "string",
- "static": false,
- "world_name": "string"
}{- "center_of_mass_m": {
- "property1": 0,
- "property2": 0
}, - "density_kg_m3": 0,
- "inertia_kgm2": {
- "property1": 0,
- "property2": 0
}, - "link_name": "string",
- "mass_kg": 0,
- "mesh_file": {
- "download_url": "string",
- "filename": "string"
}, - "model_name": "string",
- "output_file": {
- "download_url": "string",
- "filename": "string"
}
}Array of objects (Joints) | |
| mesh_format | string (Mesh Format) Default: "stl" Enum: "stl" "obj" |
| model_name | string (Model Name) Default: "model" |
required | Array of objects (Parts) non-empty |
| persist | boolean (Persist) Default: true Commit this export as a robot_description Twin work product (default on). Set false to keep the pre-MET-740 throwaway-only behavior. |
Persist Name (string) or Persist Name (null) (Persist Name) Work-product display name (default: ' | |
Project Id (string) or Project Id (null) (Project Id) Project to link the persisted work product to. | |
| static | boolean (Static) Default: false |
Update Node Id (string) or Update Node Id (null) (Update Node Id) An existing robot_description node's id — when given, this export replaces that node's content and records a new version instead of creating a new node. | |
World Name (string) or World Name (null) (World Name) |
{- "joints": [
- {
- "anchor": [
- 0,
- 0,
- 0
], - "axis": [
- 0,
- 0,
- 0
], - "base": "string",
- "follower": "string",
- "limits": {
- "property1": 0,
- "property2": 0
}, - "name": "string",
- "type": "fixed"
}
], - "mesh_format": "stl",
- "model_name": "model",
- "parts": [
- {
- "color_rgba": [
- 0,
- 0,
- 0,
- 0
], - "density_kg_m3": 0,
- "link_name": "string",
- "material": "string",
- "node_id": "string"
}
], - "persist": true,
- "persist_name": "string",
- "project_id": "string",
- "static": false,
- "update_node_id": "string",
- "world_name": "string"
}{- "joint_names": [
- "string"
], - "joints": [ ],
- "link_names": [
- "string"
], - "mesh_files": [
- {
- "download_url": "string",
- "filename": "string"
}
], - "model_name": "string",
- "output_file": {
- "download_url": "string",
- "filename": "string"
}, - "robot_description_node_id": "string"
}Density Kg M3 (number) or Density Kg M3 (null) (Density Kg M3) | |
| link_name | string (Link Name) Default: "base_link" |
Material (string) or Material (null) (Material) | |
| mesh_format | string (Mesh Format) Default: "stl" Enum: "stl" "obj" |
| mesh_uri_prefix | string (Mesh Uri Prefix) Default: "" |
| node_id required | string (Node Id) non-empty Twin work-product node id (STEP file). |
| xacro | boolean (Xacro) Default: false |
{- "density_kg_m3": 0,
- "link_name": "base_link",
- "material": "string",
- "mesh_format": "stl",
- "mesh_uri_prefix": "",
- "node_id": "string",
- "xacro": false
}{- "center_of_mass_m": {
- "property1": 0,
- "property2": 0
}, - "density_kg_m3": 0,
- "inertia_kgm2": {
- "property1": 0,
- "property2": 0
}, - "link_name": "string",
- "mass_kg": 0,
- "mesh_file": {
- "download_url": "string",
- "filename": "string"
}, - "output_file": {
- "download_url": "string",
- "filename": "string"
}
}Array of objects (Joints) | |
Mesh Angular Tolerance (number) or Mesh Angular Tolerance (null) (Mesh Angular Tolerance) Max angle (radians) between adjacent facet normals. Omit for OCCT's default. | |
| mesh_format | string (Mesh Format) Default: "stl" Enum: "stl" "obj" |
Mesh Tolerance (number) or Mesh Tolerance (null) (Mesh Tolerance) Max linear deviation (mm) between the tessellated mesh and the true CAD surface, applied to every part — smaller means more triangles on curves/fillets and less visible faceting. Omit to keep OCCT's default. | |
| mesh_uri_prefix | string (Mesh Uri Prefix) Default: "" |
required | Array of objects (Parts) non-empty |
| persist | boolean (Persist) Default: true Commit this export as a robot_description Twin work product (default on). Set false to keep the pre-MET-740 throwaway-only behavior. |
Persist Name (string) or Persist Name (null) (Persist Name) Work-product display name (default: ' | |
Project Id (string) or Project Id (null) (Project Id) Project to link the persisted work product to. | |
| robot_name | string (Robot Name) Default: "robot" |
Update Node Id (string) or Update Node Id (null) (Update Node Id) An existing robot_description node's id — when given, this export replaces that node's content and records a new version instead of creating a new node. | |
| xacro | boolean (Xacro) Default: false |
{- "joints": [
- {
- "anchor": [
- 0,
- 0,
- 0
], - "axis": [
- 0,
- 0,
- 0
], - "base": "string",
- "follower": "string",
- "limits": {
- "property1": 0,
- "property2": 0
}, - "name": "string",
- "type": "fixed"
}
], - "mesh_angular_tolerance": 1,
- "mesh_format": "stl",
- "mesh_tolerance": 1,
- "mesh_uri_prefix": "",
- "parts": [
- {
- "color_rgba": [
- 0,
- 0,
- 0,
- 0
], - "density_kg_m3": 0,
- "link_name": "string",
- "material": "string",
- "node_id": "string"
}
], - "persist": true,
- "persist_name": "string",
- "project_id": "string",
- "robot_name": "robot",
- "update_node_id": "string",
- "xacro": false
}{- "joint_names": [
- "string"
], - "joints": [ ],
- "link_names": [
- "string"
], - "mesh_files": [
- {
- "download_url": "string",
- "filename": "string"
}
], - "output_file": {
- "download_url": "string",
- "filename": "string"
}, - "robot_description_node_id": "string",
- "robot_name": "string"
}Density Kg M3 (number) or Density Kg M3 (null) (Density Kg M3) | |
Material (string) or Material (null) (Material) | |
| node_id required | string (Node Id) non-empty Twin work-product node id (STEP file). |
| prim_name | string (Prim Name) Default: "model" |
{- "density_kg_m3": 0,
- "material": "string",
- "node_id": "string",
- "prim_name": "model"
}{- "center_of_mass_m": {
- "property1": 0,
- "property2": 0
}, - "density_kg_m3": 0,
- "inertia_kgm2": {
- "property1": 0,
- "property2": 0
}, - "mass_kg": 0,
- "mesh_file": {
- "download_url": "string",
- "filename": "string"
}, - "output_file": {
- "download_url": "string",
- "filename": "string"
}, - "prim_name": "string",
- "triangle_count": 0
}Array of objects (Joints) | |
required | Array of objects (Parts) non-empty |
| persist | boolean (Persist) Default: true Commit this export as a robot_description Twin work product (default on). Set false to keep the pre-MET-740 throwaway-only behavior. |
Persist Name (string) or Persist Name (null) (Persist Name) Work-product display name (default: ' | |
Project Id (string) or Project Id (null) (Project Id) Project to link the persisted work product to. | |
| robot_name | string (Robot Name) Default: "robot" |
Update Node Id (string) or Update Node Id (null) (Update Node Id) An existing robot_description node's id — when given, this export replaces that node's content and records a new version instead of creating a new node. |
{- "joints": [
- {
- "anchor": [
- 0,
- 0,
- 0
], - "axis": [
- 0,
- 0,
- 0
], - "base": "string",
- "follower": "string",
- "limits": {
- "property1": 0,
- "property2": 0
}, - "name": "string",
- "type": "fixed"
}
], - "parts": [
- {
- "color_rgba": [
- 0,
- 0,
- 0,
- 0
], - "density_kg_m3": 0,
- "link_name": "string",
- "material": "string",
- "node_id": "string"
}
], - "persist": true,
- "persist_name": "string",
- "project_id": "string",
- "robot_name": "robot",
- "update_node_id": "string"
}{- "joint_names": [
- "string"
], - "joints": [ ],
- "link_names": [
- "string"
], - "mesh_files": [
- {
- "download_url": "string",
- "filename": "string"
}
], - "output_file": {
- "download_url": "string",
- "filename": "string"
}, - "robot_description_node_id": "string",
- "robot_name": "string"
}Author + commit a multi-part assembly from a declarative spec.
| name required | string (Name) non-empty Assembly name. |
required | Array of objects (Parts) non-empty Parts to author + assemble. |
Project Id (string) or Project Id (null) (Project Id) Project to scope the cad_model to. |
{- "name": "string",
- "parts": [
- {
- "chamfer": 1,
- "fillet": 1,
- "holes": [
- {
- "depth": 0,
- "diameter": 1,
- "x": 0,
- "y": 0
}
], - "kind": "box",
- "name": "string",
- "parameters": {
- "property1": 0,
- "property2": 0
}, - "position": [
- 0
]
}
], - "project_id": "string"
}{- "content_hash": "string",
- "minio_object_key": "string",
- "model_url": "string",
- "node_id": "string",
- "part_count": 0
}Compile a description into a spec and check it — but do NOT build (dry run).
Returns the spec for review plus any geometric-feasibility warnings, so the
caller can tweak it (or save it and run /assembly) before committing.
| description required | string (Description) non-empty Plain-English description. |
Model (string) or Model (null) (Model) LLM model override. | |
Name (string) or Name (null) (Name) Override the assembly name. | |
Provider (string) or Provider (null) (Provider) LLM provider override. |
{- "description": "string",
- "model": "string",
- "name": "string",
- "provider": "string"
}{- "buildable": true,
- "errors": [
- "string"
], - "spec": {
- "name": "string",
- "parts": [
- {
- "chamfer": 1,
- "fillet": 1,
- "holes": [
- {
- "depth": 0,
- "diameter": 1,
- "x": 0,
- "y": 0
}
], - "kind": "box",
- "name": "string",
- "parameters": {
- "property1": 0,
- "property2": 0
}, - "position": [
- 0
]
}
], - "project_id": "string"
}
}Compile a plain-English description into a spec (LLM), then build it.
The LLM only produces the small declarative spec — the geometry itself is
authored deterministically by the same builder as /assembly, so the
output is reproducible and the spec is returned for review.
| description required | string (Description) non-empty Plain-English description of the part/assembly. |
Model (string) or Model (null) (Model) LLM model override for translation. | |
Name (string) or Name (null) (Name) Override the assembly name (else the model chooses one). | |
Project Id (string) or Project Id (null) (Project Id) Project to scope the cad_model to. | |
Provider (string) or Provider (null) (Provider) LLM provider override for translation. |
{- "description": "string",
- "model": "string",
- "name": "string",
- "project_id": "string",
- "provider": "string"
}{- "content_hash": "string",
- "minio_object_key": "string",
- "model_url": "string",
- "node_id": "string",
- "part_count": 0,
- "spec": {
- "name": "string",
- "parts": [
- {
- "chamfer": 1,
- "fillet": 1,
- "holes": [
- {
- "depth": 0,
- "diameter": 1,
- "x": 0,
- "y": 0
}
], - "kind": "box",
- "name": "string",
- "parameters": {
- "property1": 0,
- "property2": 0
}, - "position": [
- 0
]
}
], - "project_id": "string"
}
}List threads with optional filtering and pagination.
Channel Id (string) or Channel Id (null) (Channel Id) Filter by channel ID | |
Scope Kind (string) or Scope Kind (null) (Scope Kind) Filter by scope kind | |
Entity Id (string) or Entity Id (null) (Entity Id) Filter by scope entity ID | |
| include_archived | boolean (Include Archived) Default: false Include archived threads |
| page | integer (Page) >= 1 Default: 1 Page number (1-indexed) |
| per_page | integer (Per Page) [ 1 .. 100 ] Default: 20 Results per page |
{- "page": 0,
- "per_page": 0,
- "threads": [
- {
- "archived": true,
- "channel_id": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "id": "string",
- "last_message_at": "2019-08-24T14:15:22Z",
- "message_count": 0,
- "scope_entity_id": "string",
- "scope_kind": "string",
- "title": "string"
}
], - "total": 0
}Create a new thread, optionally with an initial message.
Initial Message (string) or Initial Message (null) (Initial Message) If provided, a first message is created automatically | |
| scope_entity_id required | string (Scope Entity Id) ID of the scoped entity |
| scope_kind required | string (Scope Kind) Scope type (session, approval, bom-entry, ...) |
Title (string) or Title (null) (Title) Optional thread title |
{- "initial_message": "string",
- "scope_entity_id": "string",
- "scope_kind": "string",
- "title": "string"
}{- "archived": true,
- "channel_id": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "id": "string",
- "last_message_at": "2019-08-24T14:15:22Z",
- "messages": [
- {
- "actor_id": "string",
- "actor_kind": "string",
- "content": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "graph_ref_label": "string",
- "graph_ref_node": "string",
- "graph_ref_type": "string",
- "id": "string",
- "status": "string",
- "thread_id": "string",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "scope_entity_id": "string",
- "scope_kind": "string",
- "title": "string"
}Return a single thread with all its messages.
| thread_id required | string (Thread Id) |
{- "archived": true,
- "channel_id": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "id": "string",
- "last_message_at": "2019-08-24T14:15:22Z",
- "messages": [
- {
- "actor_id": "string",
- "actor_kind": "string",
- "content": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "graph_ref_label": "string",
- "graph_ref_node": "string",
- "graph_ref_type": "string",
- "id": "string",
- "status": "string",
- "thread_id": "string",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "scope_entity_id": "string",
- "scope_kind": "string",
- "title": "string"
}Append a message to an existing thread.
After persisting the user message, the handler routes it to the appropriate domain agent (when an LLM is configured). The agent's response is inserted into the thread automatically.
| thread_id required | string (Thread Id) |
| actor_id required | string (Actor Id) ID of the actor sending the message |
| actor_kind required | string (Actor Kind) Actor type: user | agent | system |
| content required | string (Content) non-empty Message content |
Graph Ref Label (string) or Graph Ref Label (null) (Graph Ref Label) Digital-twin ref label | |
Graph Ref Node (string) or Graph Ref Node (null) (Graph Ref Node) Digital-twin node reference | |
Graph Ref Type (string) or Graph Ref Type (null) (Graph Ref Type) Digital-twin ref type | |
Model (string) or Model (null) (Model) Override model for this turn | |
Provider (string) or Provider (null) (Provider) Override provider id for this turn | |
Array of Tools (strings) or Tools (null) (Tools) Enabled MCP tool ids for this turn (None = all available) |
{- "actor_id": "string",
- "actor_kind": "string",
- "content": "string",
- "graph_ref_label": "string",
- "graph_ref_node": "string",
- "graph_ref_type": "string",
- "model": "string",
- "provider": "string",
- "tools": [
- "string"
]
}{- "actor_id": "string",
- "actor_kind": "string",
- "content": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "graph_ref_label": "string",
- "graph_ref_node": "string",
- "graph_ref_type": "string",
- "id": "string",
- "status": "string",
- "thread_id": "string",
- "updated_at": "2019-08-24T14:15:22Z"
}Rescope an EXISTING thread in place (MET-580).
Unlike POST /threads, this preserves the conversation — the same
thread continues, and its next turn's project brief (if scoped to a
project) reflects the new scope. The agent-callable chat.set_project_scope
native tool goes through the same apply_thread_scope helper, so a human
hitting this endpoint and the agent switching scope mid-turn behave
identically and both broadcast the same scope.changed SSE event.
| thread_id required | string (Thread Id) |
| scope_entity_id required | string (Scope Entity Id) ID of the scoped entity |
| scope_kind required | string (Scope Kind) Scope type (session, approval, project, ...) |
{- "scope_entity_id": "string",
- "scope_kind": "string"
}{- "archived": true,
- "channel_id": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "id": "string",
- "last_message_at": "2019-08-24T14:15:22Z",
- "messages": [
- {
- "actor_id": "string",
- "actor_kind": "string",
- "content": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "graph_ref_label": "string",
- "graph_ref_node": "string",
- "graph_ref_type": "string",
- "id": "string",
- "status": "string",
- "thread_id": "string",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "scope_entity_id": "string",
- "scope_kind": "string",
- "title": "string"
}Stream real-time events for a chat thread via Server-Sent Events.
The client receives events as they occur:
message.created -- a new message was addedagent.typing -- an agent is processingcontext.stats -- the turn's context-window snapshot: tokens used vs.
the model's window, broken down by system prompt / project brief / history /
tool schemas / message, with included-vs-available counts (harness turns)agent.step -- one reasoning/tool-call step in the agent's tracemessage.delta -- one token/chunk of the streaming answeragent.done -- an agent finishederror -- an error occurredThe connection stays open until the client disconnects or the server closes the stream.
| thread_id required | string (Thread Id) |
nullTool-call approvals: the ones awaiting a decision, or every one.
Overdue holds are expired first, so nothing listed as pending is a call
whose waiter has already given up (FORGE-466). status=all is the audit
view (FORGE-473): every entry whichever route answered it, each carrying
its route, outcome and approver.
project_id scopes the queue. Unscoped before, so a reviewer working
on one project saw every project's held writes in one list -- and a held
write names a tool and a caller, not a product, so there was no way to
tell from the row which one it belonged to. Approvals whose tool call
carried no project are counted rather than dropped: an approval that
quietly disappears is the one nobody answers.
| status | string (Status) Default: "pending" Enum: "pending" "all" |
Project Id (string) or Project Id (null) (Project Id) |
{- "runs": [
- {
- "approval_deadline": 0,
- "approval_reason": "string",
- "approved_by": "string",
- "approver_verified": false,
- "created_at": 0,
- "engine": "string",
- "error": "string",
- "flow_content_hash": "string",
- "flow_version_id": "string",
- "history": [
- "string"
], - "id": "string",
- "project_id": "string",
- "request": { },
- "result": { },
- "status": "string",
- "updated_at": 0,
- "usage": { }
}
], - "unscoped_count": 0
}Create a held approval and return it.
This exists because the approval store is process-level (an
InMemoryRunStore in this process), and the MCP sidecar is a
different process. Before FORGE-406 a sidecar could only hold calls in
its own memory, where the dashboard — served from here — would never see
them. So the sidecar parks them here instead, and there is exactly one
ledger rather than one per process.
A second store would have been the more obvious fix and the wrong one: two queues means a reviewer clearing one while the other fills, and no page that shows both.
object (Arguments) | |
| caller | string (Caller) Default: "untrusted" |
Client (string) or Client (null) (Client) | |
Project (string) or Project (null) (Project) | |
| reason required | string (Reason) |
| route | string (Route) Default: "dashboard" Enum: "dashboard" "elicitation" |
Session Id (string) or Session Id (null) (Session Id) | |
| source | string (Source) Default: "mcp" |
Timeout Seconds (number) or Timeout Seconds (null) (Timeout Seconds) | |
| tool required | string (Tool) |
{- "arguments": { },
- "caller": "untrusted",
- "client": "string",
- "project": "string",
- "reason": "string",
- "route": "dashboard",
- "session_id": "string",
- "source": "mcp",
- "timeout_seconds": 1,
- "tool": "string"
}{- "approval_deadline": 0,
- "approval_reason": "string",
- "approved_by": "string",
- "approver_verified": false,
- "created_at": 0,
- "engine": "string",
- "error": "string",
- "flow_content_hash": "string",
- "flow_version_id": "string",
- "history": [
- "string"
], - "id": "string",
- "project_id": "string",
- "request": { },
- "result": { },
- "status": "string",
- "updated_at": 0,
- "usage": { }
}{- "approval_deadline": 0,
- "approval_reason": "string",
- "approved_by": "string",
- "approver_verified": false,
- "created_at": 0,
- "engine": "string",
- "error": "string",
- "flow_content_hash": "string",
- "flow_version_id": "string",
- "history": [
- "string"
], - "id": "string",
- "project_id": "string",
- "request": { },
- "result": { },
- "status": "string",
- "updated_at": 0,
- "usage": { }
}Record the decision, attributed to whoever made this request.
The identity comes from the request, not the body (FORGE-393). A client cannot nominate the approver, which is the whole point: some tools write the approver's name down as their result.
| run_id required | string (Run Id) |
| decision required | string (Decision) Enum: "approve" "reject" "retry" "rework" |
| reason | string (Reason) <= 2000 characters Default: "" |
| to_phase | string (To Phase) <= 200 characters Default: "" |
{- "decision": "approve",
- "reason": "",
- "to_phase": ""
}{- "approval_deadline": 0,
- "approval_reason": "string",
- "approved_by": "string",
- "approver_verified": false,
- "created_at": 0,
- "engine": "string",
- "error": "string",
- "flow_content_hash": "string",
- "flow_version_id": "string",
- "history": [
- "string"
], - "id": "string",
- "project_id": "string",
- "request": { },
- "result": { },
- "status": "string",
- "updated_at": 0,
- "usage": { }
}The waiting side stopped waiting: close the hold so nobody answers it.
Idempotent. A hold already timed_out or canceled comes back as it
is with 200, so a retry after a lost response is harmless. A hold a human
already decided is 409: the waiter must read the decision back rather
than overwrite it, because an approval that landed in the last instant is
still an approval.
| run_id required | string (Run Id) |
Approver (string) or Approver (null) (Approver) | |
| approver_verified | boolean (Approver Verified) Default: false |
| outcome required | string (Outcome) Enum: "timed_out" "canceled" "approved" "rejected" |
Reason (string) or Reason (null) (Reason) |
{- "approver": "string",
- "approver_verified": false,
- "outcome": "timed_out",
- "reason": "string"
}{- "approval_deadline": 0,
- "approval_reason": "string",
- "approved_by": "string",
- "approver_verified": false,
- "created_at": 0,
- "engine": "string",
- "error": "string",
- "flow_content_hash": "string",
- "flow_version_id": "string",
- "history": [
- "string"
], - "id": "string",
- "project_id": "string",
- "request": { },
- "result": { },
- "status": "string",
- "updated_at": 0,
- "usage": { }
}Tasks, open ones by default. status=all lists every state.
Status (string) or Status (null) (Status) Default: "open" | |
Project Id (string) or Project Id (null) (Project Id) | |
Run Id (string) or Run Id (null) (Run Id) |
{ }Post a phase for the client. Idempotent on run:phase:attempt.
| attempt | integer (Attempt) Default: 1 |
object (Brief) | |
| phaseId required | string (Phaseid) |
Projectid (string) or Projectid (null) (Projectid) | |
| runId required | string (Runid) |
{- "attempt": 1,
- "brief": { },
- "phaseId": "string",
- "projectId": "string",
- "runId": "string"
}{ }Withdraw a task nobody will wait on any more (the worker's activity ended).
| task_id required | string (Task Id) |
| reason | string (Reason) Default: "" |
{- "reason": ""
}{ }Take a task and get its full brief.
| task_id required | string (Task Id) |
| client | string (Client) Default: "" |
{- "client": ""
}{ }Hand the phase back. The run's gate then checks the twin as usual.
| task_id required | string (Task Id) |
| artifacts | Array of strings (Artifacts) |
| client | string (Client) Default: "" |
| summary required | string (Summary) |
{- "artifacts": [
- "string"
], - "client": "",
- "summary": "string"
}{ }Generate a compliance checklist for the given project and markets.
Markets are provided as a comma-separated query parameter, e.g.
?markets=UKCA,CE,FCC.
| project_id required | string (Project Id) |
| markets | string (Markets) Default: "UKCA,CE" Comma-separated regime codes |
{- "coverage_percent": 0,
- "evidenced_items": 0,
- "items": [
- { }
], - "project_id": "string",
- "target_markets": [
- "string"
], - "total_items": 0
}Link a piece of evidence to a compliance checklist item.
| project_id required | string (Project Id) |
| checklist_item_id required | string (Checklist Item Id) Checklist item ID to link to |
| description | string (Description) Default: "" Evidence description |
| evidence_type required | string (EvidenceType) Enum: "TEST_REPORT" "DECLARATION" "CERTIFICATE" "TECHNICAL_FILE" "RISK_ASSESSMENT" Type of evidence |
| title required | string (Title) Evidence title |
Work Product Id (string) or Work Product Id (null) (Work Product Id) WorkProduct UUID |
{- "checklist_item_id": "string",
- "description": "",
- "evidence_type": "TEST_REPORT",
- "title": "string",
- "work_product_id": "224aa6cf-be77-49ce-9c79-b3b38dfd40f4"
}{- "checklist_item_id": "string",
- "description": "string",
- "evidence_type": "string",
- "id": "string",
- "status": "string",
- "title": "string",
- "uploaded_at": "string"
}Retrieve all evidence records for a checklist item.
| project_id required | string (Project Id) |
| item_id required | string (Item Id) |
[- {
- "checklist_item_id": "string",
- "description": "string",
- "evidence_type": "string",
- "id": "string",
- "status": "string",
- "title": "string",
- "uploaded_at": "string"
}
]required | Array of objects (Candidates) |
| category required | string (Category) |
Projectid (string) or Projectid (null) (Projectid) | |
| purchaseUnit required | string (Purchaseunit) |
| quantity | integer (Quantity) Default: 1 |
| rationale required | string (Rationale) |
required | object (Requiredspecs) |
Array of Requirementids (strings) or Requirementids (null) (Requirementids) | |
| selectedMpn required | string (Selectedmpn) |
| title required | string (Title) |
{- "candidates": [
- {
- "manufacturer": "string",
- "mpn": "string",
- "specs": { }
}
], - "category": "string",
- "projectId": "string",
- "purchaseUnit": "string",
- "quantity": 1,
- "rationale": "string",
- "requiredSpecs": {
- "property1": {
- "op": "string",
- "value": 0
}, - "property2": {
- "op": "string",
- "value": 0
}
}, - "requirementIds": [
- "string"
], - "selectedMpn": "string",
- "title": "string"
}{ }Turn a drag delta into a parametric constraint, and — when the dragged
group maps to a live FreeCAD session object (session_id + obj_id) —
bind it into the model so Apply re-parameterizes and re-solves (MET-531).
Without a session/object it stays a suggestion-only stub (MET-519). Binding
is best-effort: an adapter error leaves the suggestion intact with
bound=False and a binding_error.
required | object (DeltaTransform) Delta of a dragged group: translation (mm), or — additively, MET-611 — a single-axis rotation or scale. Exactly one kind is populated per request, matching whichever gizmo mode produced it on the client. |
| group_name required | string (Group Name) non-empty |
Obj Id (string) or Obj Id (null) (Obj Id) | |
Property Path (string) or Property Path (null) (Property Path) | |
Session Id (string) or Session Id (null) (Session Id) |
{- "delta": {
- "dx": 0,
- "dy": 0,
- "dz": 0,
- "rotation": {
- "angle_deg": 0,
- "axis": "x"
}, - "scale": {
- "axis": "x",
- "factor": 0
}
}, - "group_name": "string",
- "obj_id": "string",
- "property_path": "string",
- "session_id": "string"
}{- "binding_error": "string",
- "bound": false,
- "conflict_reason": "string",
- "constraint": {
- "parameter": "string",
- "unit": "mm",
- "value": 0
}, - "expression": "string",
- "status": "ok",
- "suggestion": "string"
}| work_product_id required | string (Work Product Id) |
| requirement_mm | number (Requirement Mm) Default: 0.5 |
End Effector Part (string) or End Effector Part (null) (End Effector Part) |
{- "contributions": [
- {
- "actuator": "string",
- "contribution_mm": 0,
- "jacobian_mm_per_rad": 0,
- "joint_name": "string",
- "resolution_rad": 0,
- "source": "string"
}
], - "passes": true,
- "requirement_mm": 0,
- "target_part": "string",
- "warnings": [
- "string"
], - "work_product_id": "string",
- "worst_case_repeatability_mm": 0
}Upload a STEP/IGES file and convert to GLB.
Returns the conversion result with a URL to the GLB file and metadata. Results are cached by content hash — re-uploading the same file is instant.
| quality | string (Quality) ^(preview|standard|fine)$ Default: "standard" |
| file required | string <application/octet-stream> (File) STEP or IGES CAD file |
{- "cached": true,
- "glb_url": "string",
- "hash": "string",
- "metadata": { }
}Retrieve a cached conversion result by content hash.
| file_hash required | string (File Hash) |
| quality | string (Quality) ^(preview|standard|fine)$ Default: "standard" |
{- "cached": true,
- "glb_url": "string",
- "hash": "string",
- "metadata": { }
}{- "defaultFlowId": "string",
- "flows": [
- {
- "description": "string",
- "graph": { },
- "id": "string",
- "isDefault": false,
- "label": "string",
- "name": "string",
- "phases": [
- {
- "condition": "string",
- "dependsOn": [
- "string"
], - "disciplines": [
- "string"
], - "enforceDeliverables": true,
- "expectedArtifacts": [
- "string"
], - "gate": {
- "autoApprove": false,
- "criteria": [
- "string"
], - "enforceConstraints": false,
- "gateId": "string",
- "name": "string"
}, - "id": "string",
- "model": "string",
- "objective": "string",
- "outcome": "",
- "requiredDeliverables": [
- "string"
], - "slots": [
- {
- "derived": false,
- "itemKey": "string",
- "itemType": "string",
- "name": "string"
}
], - "title": "string"
}
], - "valid": true,
- "version": "string",
- "violations": [
- "string"
]
}
]
}Compile an intent into its structured model. Stores nothing, starts nothing.
Deterministic: no model call, and no value the person did not state. Use it before proposing to see what was understood and what is still unknown.
Budget (string) or Budget (null) (Budget) | |
CallerBody (object) or null | |
| intent required | string (Intent) |
Loadsanduse (string) or Loadsanduse (null) (Loadsanduse) | |
ManufacturingContextBody (object) or null | |
Model (string) or Model (null) (Model) | |
Array of Operations (objects) or Operations (null) (Operations) | |
Projectid (string) or Projectid (null) (Projectid) | |
Provider (string) or Provider (null) (Provider) | |
| requirements | Array of strings (Requirements) |
TargetMaturity (string) or null | |
Template (string) or Template (null) (Template) |
{- "budget": "string",
- "caller": {
- "client": "string",
- "model": "string"
}, - "intent": "string",
- "loadsAndUse": "string",
- "manufacturingContext": {
- "machines": [
- "string"
], - "processes": [
- "string"
], - "productionQuantity": 1,
- "route": "in_house",
- "stockMaterials": [
- "string"
]
}, - "model": "string",
- "operations": [
- {
- "op": "string",
- "phase": "string",
- "rationale": "",
- "value": { }
}
], - "projectId": "string",
- "provider": "string",
- "requirements": [
- "string"
], - "targetMaturity": "concept",
- "template": "string"
}{- "intent": { },
- "missingInputs": [
- {
- "answerType": "string",
- "field": "",
- "id": "string",
- "options": [
- "string"
], - "question": "string",
- "required": true,
- "source": "metaforge",
- "why": "string"
}
]
}Tailor a template to a project's intent, and hold it for a human.
The response carries an approvalId, not a run. FORGE-398's rule is
that nothing starts before approval, and the way to make that true is for
the endpoint that generates a flow to have no ability to start one.
If the manufacturing route, target maturity or loads are missing, the
answer is 200 with status: "needs_input" and the questions to
answer -- no flow, no stored version, no held approval (FORGE-463). A
generator that guesses those produces a flow that reads as tailored and
is not.
Budget (string) or Budget (null) (Budget) | |
CallerBody (object) or null | |
| intent required | string (Intent) |
Loadsanduse (string) or Loadsanduse (null) (Loadsanduse) | |
ManufacturingContextBody (object) or null | |
Model (string) or Model (null) (Model) | |
Array of Operations (objects) or Operations (null) (Operations) | |
Projectid (string) or Projectid (null) (Projectid) | |
Provider (string) or Provider (null) (Provider) | |
| requirements | Array of strings (Requirements) |
TargetMaturity (string) or null | |
Template (string) or Template (null) (Template) |
{- "budget": "string",
- "caller": {
- "client": "string",
- "model": "string"
}, - "intent": "string",
- "loadsAndUse": "string",
- "manufacturingContext": {
- "machines": [
- "string"
], - "processes": [
- "string"
], - "productionQuantity": 1,
- "route": "in_house",
- "stockMaterials": [
- "string"
]
}, - "model": "string",
- "operations": [
- {
- "op": "string",
- "phase": "string",
- "rationale": "",
- "value": { }
}
], - "projectId": "string",
- "provider": "string",
- "requirements": [
- "string"
], - "targetMaturity": "concept",
- "template": "string"
}{- "intent": "string",
- "message": "string",
- "notes": [
- "string"
], - "questions": [
- {
- "answerType": "string",
- "field": "",
- "id": "string",
- "options": [
- "string"
], - "question": "string",
- "required": true,
- "source": "metaforge",
- "why": "string"
}
], - "status": "needs_input"
}Check an edit without saving it.
The editor calls this as the canvas changes, so a person sees a rule break while they are looking at the thing that broke it -- rather than at save, by which point they have made five more changes and have to work out which one the message is about.
| baseTemplateId required | string (Basetemplateid) |
Name (string) or Name (null) (Name) | |
required | Array of objects (Phases) |
{- "baseTemplateId": "string",
- "name": "string",
- "phases": [
- {
- "condition": "string",
- "dependsOn": [
- "string"
], - "disciplines": [
- "string"
], - "enforceDeliverables": true,
- "expectedArtifacts": [
- "string"
], - "gate": {
- "autoApprove": false,
- "criteria": [
- "string"
], - "enforceConstraints": false,
- "gateId": "string",
- "name": "string"
}, - "id": "string",
- "model": "string",
- "objective": "string",
- "outcome": "",
- "requiredDeliverables": [
- "string"
], - "slots": [
- {
- "derived": false,
- "itemKey": "",
- "itemType": "string",
- "name": "string"
}
], - "title": "string"
}
]
}{- "valid": true,
- "violations": [
- "string"
]
}Save an edit as a new version, held for approval.
Never mutates an existing version. A run pins the version it started on, so a version changing underneath would make a completed run's provenance a lie -- and an approval that can be edited afterwards is not an approval.
| baseTemplateId required | string (Basetemplateid) |
Name (string) or Name (null) (Name) | |
required | Array of objects (Phases) |
{- "baseTemplateId": "string",
- "name": "string",
- "phases": [
- {
- "condition": "string",
- "dependsOn": [
- "string"
], - "disciplines": [
- "string"
], - "enforceDeliverables": true,
- "expectedArtifacts": [
- "string"
], - "gate": {
- "autoApprove": false,
- "criteria": [
- "string"
], - "enforceConstraints": false,
- "gateId": "string",
- "name": "string"
}, - "id": "string",
- "model": "string",
- "objective": "string",
- "outcome": "",
- "requiredDeliverables": [
- "string"
], - "slots": [
- {
- "derived": false,
- "itemKey": "",
- "itemType": "string",
- "name": "string"
}
], - "title": "string"
}
]
}{- "approvalId": "string",
- "baseTemplateId": "string",
- "baseVersion": "string",
- "changes": [
- "string"
], - "context": "",
- "flow": {
- "description": "string",
- "graph": { },
- "id": "string",
- "isDefault": false,
- "label": "string",
- "name": "string",
- "phases": [
- {
- "condition": "string",
- "dependsOn": [
- "string"
], - "disciplines": [
- "string"
], - "enforceDeliverables": true,
- "expectedArtifacts": [
- "string"
], - "gate": {
- "autoApprove": false,
- "criteria": [
- "string"
], - "enforceConstraints": false,
- "gateId": "string",
- "name": "string"
}, - "id": "string",
- "model": "string",
- "objective": "string",
- "outcome": "",
- "requiredDeliverables": [
- "string"
], - "slots": [
- {
- "derived": false,
- "itemKey": "string",
- "itemType": "string",
- "name": "string"
}
], - "title": "string"
}
], - "valid": true,
- "version": "string",
- "violations": [
- "string"
]
}, - "origin": "string",
- "status": "string",
- "valid": true,
- "versionId": "string",
- "violations": [
- "string"
]
}{- "approvalId": "string",
- "baseTemplateId": "string",
- "baseVersion": "string",
- "changes": [
- "string"
], - "context": "",
- "flow": {
- "description": "string",
- "graph": { },
- "id": "string",
- "isDefault": false,
- "label": "string",
- "name": "string",
- "phases": [
- {
- "condition": "string",
- "dependsOn": [
- "string"
], - "disciplines": [
- "string"
], - "enforceDeliverables": true,
- "expectedArtifacts": [
- "string"
], - "gate": {
- "autoApprove": false,
- "criteria": [
- "string"
], - "enforceConstraints": false,
- "gateId": "string",
- "name": "string"
}, - "id": "string",
- "model": "string",
- "objective": "string",
- "outcome": "",
- "requiredDeliverables": [
- "string"
], - "slots": [
- {
- "derived": false,
- "itemKey": "string",
- "itemType": "string",
- "name": "string"
}
], - "title": "string"
}
], - "valid": true,
- "version": "string",
- "violations": [
- "string"
]
}, - "origin": "string",
- "status": "string",
- "valid": true,
- "versionId": "string",
- "violations": [
- "string"
]
}The same assessment for a saved (tailored or edited) flow version.
| version_id required | string (Version Id) |
Profile (string) or Profile (null) (Profile) |
{- "flowId": "string",
- "profile": "string",
- "report": { },
- "versionId": "string"
}One flow, for the run detail view and the plan canvas.
| flow_id required | string (Flow Id) |
{- "description": "string",
- "graph": { },
- "id": "string",
- "isDefault": false,
- "label": "string",
- "name": "string",
- "phases": [
- {
- "condition": "string",
- "dependsOn": [
- "string"
], - "disciplines": [
- "string"
], - "enforceDeliverables": true,
- "expectedArtifacts": [
- "string"
], - "gate": {
- "autoApprove": false,
- "criteria": [
- "string"
], - "enforceConstraints": false,
- "gateId": "string",
- "name": "string"
}, - "id": "string",
- "model": "string",
- "objective": "string",
- "outcome": "",
- "requiredDeliverables": [
- "string"
], - "slots": [
- {
- "derived": false,
- "itemKey": "string",
- "itemType": "string",
- "name": "string"
}
], - "title": "string"
}
], - "valid": true,
- "version": "string",
- "violations": [
- "string"
]
}Can this template be run with the tools that exist and answer right now?
profile narrows coverage to the tools a client connected with that
MCP profile is served.
| flow_id required | string (Flow Id) |
Profile (string) or Profile (null) (Profile) |
{- "flowId": "string",
- "profile": "string",
- "report": { },
- "versionId": "string"
}| deflectionLimitMm required | number (Deflectionlimitmm) |
| loadN required | number (Loadn) |
| material | string (Material) Default: "aluminum_6061" |
| maxIterations | integer (Maxiterations) Default: 60 |
Projectid (string) or Projectid (null) (Projectid) | |
Array of Requirementids (strings) or Requirementids (null) (Requirementids) | |
| sfLimit | number (Sflimit) Default: 2 |
Wallmaxmm (number) or Wallmaxmm (null) (Wallmaxmm) | |
| wallMinMm | number (Wallminmm) Default: 0.5 |
| workProductId required | string (Workproductid) |
{- "deflectionLimitMm": 0,
- "loadN": 0,
- "material": "aluminum_6061",
- "maxIterations": 60,
- "projectId": "string",
- "requirementIds": [
- "string"
], - "sfLimit": 2,
- "wallMaxMm": 0,
- "wallMinMm": 0.5,
- "workProductId": "string"
}{ }Array of Build Axis (numbers) or Build Axis (null) (Build Axis) | |
| mesh_file required | string (Mesh File) |
Project Id (string) or Project Id (null) (Project Id) | |
Threshold Deg (number) or Threshold Deg (null) (Threshold Deg) | |
| work_product_id required | string (Work Product Id) |
{- "build_axis": [
- 0
], - "mesh_file": "string",
- "project_id": "string",
- "threshold_deg": 0,
- "work_product_id": "string"
}{- "build_axis": [
- 0
], - "dfm_pass": true,
- "evidence_node_id": "string",
- "faces": [
- {
- "area_mm2": 0,
- "flagged": true,
- "name": "string",
- "normal": [
- 0
], - "tilt_from_vertical_deg": 0
}
], - "flagged_count": 0,
- "threshold_deg": 0,
- "total_faces": 0
}| adapter | string (Adapter) Default: "freecad" |
| commit | boolean (Commit) Default: true |
required | object (Feature) |
| material | string (Material) Default: "aluminum_6061" |
| name required | string (Name) |
Projectid (string) or Projectid (null) (Projectid) | |
Workproductid (string) or Workproductid (null) (Workproductid) |
{- "adapter": "freecad",
- "commit": true,
- "feature": { },
- "material": "aluminum_6061",
- "name": "string",
- "projectId": "string",
- "workProductId": "string"
}{ }{- "added": { },
- "changed": {
- "property1": {
- "from_value": null,
- "to_value": null
}, - "property2": {
- "from_value": null,
- "to_value": null
}
}, - "currentWorkProductId": "string",
- "previousWorkProductId": "string",
- "removed": { }
}Project Id (string) or Project Id (null) (Project Id) | |
| work_product_id required | string (Work Product Id) |
{- "project_id": "string",
- "work_product_id": "string"
}{- "firmware_source_node_id": "string",
- "joints": [
- {
- "can_id": 0,
- "joint_name": "string",
- "joint_type": "string",
- "limits": {
- "property1": 0,
- "property2": 0
}
}
], - "pinmap_node_id": "string"
}Store a provider credential so the runtime uses it (no restart).
api_key → the gateway auth store; oauth → the Codex auth.json the
codex adapter reads. Validates the provider against the registry.
X-Metaforge-Admin (string) or X-Metaforge-Admin (null) (X-Metaforge-Admin) |
Api Key (string) or Api Key (null) (Api Key) Raw API key (method=api_key). | |
Base Url (string) or Base Url (null) (Base Url) Optional base_url override. | |
| method | string (Method) Default: "api_key" 'api_key' or 'oauth'. |
| provider required | string (Provider) Registry provider id (e.g. openai, openai-codex). |
Tokens (object) or Tokens (null) (Tokens) OAuth token blob in auth.json shape (method=oauth). |
{- "api_key": "string",
- "base_url": "string",
- "method": "api_key",
- "provider": "string",
- "tokens": { }
}{- "method": "string",
- "ok": true,
- "provider": "string"
}Forget a provider's stored API key (and clear the selection if it pointed there).
| provider required | string (Provider) |
X-Metaforge-Admin (string) or X-Metaforge-Admin (null) (X-Metaforge-Admin) |
{- "method": "string",
- "ok": true,
- "provider": "string"
}{- "active_model": "string",
- "active_provider": "string",
- "fallback_count": 0,
- "last_fallback": {
- "at": "string",
- "error": "string",
- "fallback": "string",
- "fallback_model": "string",
- "primary": "string",
- "primary_model": "string",
- "reason": "string",
- "role": "string"
}, - "providers": [
- {
- "base_url": "string",
- "configured": true,
- "family": "string",
- "id": "string"
}
]
}Which provider and model each role is routed to.
A role absent from roles is served by the durable harness selection
(default_provider / default_model).
Project Id (string) or Project Id (null) (Project Id) |
{- "default_model": "string",
- "default_provider": "string",
- "effective_for_project": {
- "property1": {
- "configured": true,
- "model": "string",
- "provider": "string"
}, - "property2": {
- "configured": true,
- "model": "string",
- "provider": "string"
}
}, - "problems": [
- "string"
], - "projects": {
- "property1": {
- "property1": {
- "configured": true,
- "model": "string",
- "provider": "string"
}, - "property2": {
- "configured": true,
- "model": "string",
- "provider": "string"
}
}, - "property2": {
- "property1": {
- "configured": true,
- "model": "string",
- "provider": "string"
}, - "property2": {
- "configured": true,
- "model": "string",
- "provider": "string"
}
}
}, - "roles": {
- "property1": {
- "configured": true,
- "model": "string",
- "provider": "string"
}, - "property2": {
- "configured": true,
- "model": "string",
- "provider": "string"
}
}
}Set the durable active provider/model (overrides the METAFORGE_LLM_* env).
X-Metaforge-Admin (string) or X-Metaforge-Admin (null) (X-Metaforge-Admin) |
Model (string) or Model (null) (Model) Optional model id. | |
| provider required | string (Provider) Registry provider id to make active. |
{- "model": "string",
- "provider": "string"
}{- "method": "string",
- "ok": true,
- "provider": "string"
}Ingest a document (markdown / plain text) via the L1 KnowledgeService.
Backs the forge ingest <path> CLI (MET-336). Heading-aware
chunking, dedup, and citation metadata are handled by the
underlying provider — the route is a thin pass-through.
| content required | string (Content) non-empty |
| knowledgeType required | string (KnowledgeType) Enum: "design_decision" "component" "failure" "constraint" "session" Categories of knowledge stored in the knowledge layer. |
object (Metadata) | |
Projectid (string) or Projectid (null) (Projectid) | |
| sourcePath required | string (Sourcepath) non-empty |
Sourceworkproductid (string) or Sourceworkproductid (null) (Sourceworkproductid) |
{- "content": "string",
- "knowledgeType": "design_decision",
- "metadata": { },
- "projectId": "5a8591dd-4039-49df-9202-96385ba3eff8",
- "sourcePath": "string",
- "sourceWorkProductId": "c83b8d43-b0ff-4427-847b-87295ebb87a8"
}{- "chunksIndexed": 0,
- "entryIds": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "sourcePath": "string"
}Manually ingest a knowledge entry.
Routes through KnowledgeService when available so the chunk
pipeline, dedup, and citation-field round-trip apply consistently
with /documents and /search (MET-390).
| content required | string (Content) non-empty |
| knowledgeType required | string (KnowledgeType) Enum: "design_decision" "component" "failure" "constraint" "session" Categories of knowledge stored in the knowledge layer. |
object (Metadata) | |
Projectid (string) or Projectid (null) (Projectid) | |
Sourcepath (string) or Sourcepath (null) (Sourcepath) | |
Sourceworkproductid (string) or Sourceworkproductid (null) (Sourceworkproductid) |
{- "content": "string",
- "knowledgeType": "design_decision",
- "metadata": { },
- "projectId": "5a8591dd-4039-49df-9202-96385ba3eff8",
- "sourcePath": "string",
- "sourceWorkProductId": "c83b8d43-b0ff-4427-847b-87295ebb87a8"
}{- "embedded": true,
- "entryId": "09a8b554-45ca-4bab-a638-265db4b3e828"
}Semantic search over indexed knowledge.
Routes through KnowledgeService when available so it shares
the same backend as /ingest and /documents (MET-390).
project_id (MET-670) mirrors the projectId filter already
on /sources and /ingest: without it, KnowledgeService.search
falls back to the default tenant, so a project-scoped ingest was
never searchable from a project-scoped UI — the search box silently
returned whatever unrelated content happened to live under
default instead of the active project's own sources.
| query required | string (Query) non-empty Search query |
KnowledgeType (string) or Knowledgetype (null) (Knowledgetype) Filter by knowledge type | |
Projectid (string) or Projectid (null) (Projectid) Scope search to a project UUID | |
| limit | integer (Limit) [ 1 .. 50 ] Default: 5 Max results |
{- "query": "string",
- "results": [
- {
- "chunkIndex": 0,
- "content": "string",
- "createdAt": "2019-08-24T14:15:22Z",
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "knowledgeType": "design_decision",
- "metadata": { },
- "sourcePath": "string",
- "sourceWorkProductId": "c83b8d43-b0ff-4427-847b-87295ebb87a8",
- "totalChunks": 0
}
], - "totalFound": 0
}List ingested knowledge sources via KnowledgeService.list_sources().
Backs the forge sources list CLI (MET-411). Mirrors the schema
surfaced by the metaforge://knowledge/sources MCP resource.
KnowledgeType (string) or Knowledgetype (null) (Knowledgetype) Filter by knowledge type | |
Projectid (string) or Projectid (null) (Projectid) Filter by project UUID | |
| limit | integer (Limit) [ 1 .. 1000 ] Default: 100 Max sources to return |
| offset | integer (Offset) >= 0 Default: 0 Pagination offset |
{- "sources": [
- {
- "fragmentCount": 0,
- "indexedAt": "2019-08-24T14:15:22Z",
- "knowledgeType": "string",
- "metadata": { },
- "sourcePath": "string"
}
], - "total": 0
}Delete every chunk for a source via KnowledgeService.delete_by_source().
Backs the forge sources delete CLI (MET-411). Returns the
chunk count the backend removed; 0 when the source was already
absent — callers treat that as a no-op rather than an error.
| source_path required | string (Source Path) |
{- "deletedChunks": 0,
- "sourcePath": "string"
}Per-source detail — looks up by exact source_path match.
No dedicated single-source accessor exists in the KnowledgeService
contract, so we list and find — fine for CLI usage where the user
has already picked a known path. Returns 404 when the source isn't
registered.
| source_path required | string (Source Path) |
Projectid (string) or Projectid (null) (Projectid) Filter by project UUID |
{- "chunks": [
- { }
], - "fragmentCount": 0,
- "indexedAt": "2019-08-24T14:15:22Z",
- "knowledgeType": "string",
- "metadata": { },
- "sourcePath": "string"
}Retrieve a single knowledge entry by ID.
| entry_id required | string <uuid> (Entry Id) |
{- "chunkIndex": 0,
- "content": "string",
- "createdAt": "2019-08-24T14:15:22Z",
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "knowledgeType": "design_decision",
- "metadata": { },
- "sourcePath": "string",
- "sourceWorkProductId": "c83b8d43-b0ff-4427-847b-87295ebb87a8",
- "totalChunks": 0
}| work_product_id required | string (Work Product Id) |
| process required | string (Process) '3d_print' (-> STL) or 'cnc' (-> STEP) |
{- "content_base64": "string",
- "file_size_bytes": 0,
- "filename": "string",
- "format": "string",
- "process": "string",
- "work_product_id": "string"
}Component usage / relationship knowledge for name (MET-471).
Wraps MemoryClient.get_component_context: a typed convenience
over the L1 knowledge base keyed off KnowledgeType.COMPONENT.
Empty / whitespace name returns 422 — that's a client bug, not
a backend gap.
| name required | string (Name) |
| limit | integer (Limit) [ 1 .. 50 ] Default: 5 |
Projectid (string) or Projectid (null) (Projectid) |
{- "hits": [
- {
- "chunkIndex": 0,
- "content": "string",
- "heading": "string",
- "knowledgeType": "string",
- "similarityScore": 0,
- "sourcePath": "string",
- "sourceWorkProductId": "c83b8d43-b0ff-4427-847b-87295ebb87a8",
- "totalChunks": 0
}
], - "query": "string",
- "totalFound": 0
}Trigger one consolidation pass synchronously.
Defaults to on_demand mode (manual triage with the importance
floor relaxed). Pass mode=background to run the standard pass
or mode=janitor to re-validate previously-stored insights
without synthesizing new ones.
Fetchlimit (integer) or Fetchlimit (null) (Fetchlimit) | |
Minimportance (number) or Minimportance (null) (Minimportance) | |
| mode | string (ConsolidationMode) Default: "on_demand" Enum: "background" "on_demand" "proactive" "janitor" Consolidation mode. Defaults to on_demand since the REST endpoint is a manual trigger; the Temporal worker handles background. |
Projectid (string) or Projectid (null) (Projectid) | |
Since (string) or Since (null) (Since) | |
ConsolidationTheme (string) or null | |
Until (string) or Until (null) (Until) |
{- "fetchLimit": 1,
- "minImportance": 0,
- "mode": "background",
- "projectId": "5a8591dd-4039-49df-9202-96385ba3eff8",
- "since": "2019-08-24T14:15:22Z",
- "theme": "mechanical_validation",
- "until": "2019-08-24T14:15:22Z"
}{- "acceptedCount": 0,
- "fetchedCount": 0,
- "groupCount": 0,
- "mode": "background",
- "newlyFailedCount": 0,
- "rejectedCount": 0,
- "rejectedReasons": [
- "string"
], - "revalidatedCount": 0,
- "synthesizedCount": 0
}List consolidated insights, newest first.
Excludes STALE_WARN insights by default — agents should act on
fresh lessons. Pass includeStale=true for an audit / review view
that includes faded insights. Optional theme narrows to one
consolidation theme.
ConsolidationTheme (string) or Theme (null) (Theme) | |
| includeStale | boolean (Includestale) Default: false |
| limit | integer (Limit) [ 1 .. 500 ] Default: 50 |
{- "includeStale": true,
- "insights": [
- {
- "confidence": 0,
- "confidenceTier": "verbatim",
- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "kind": "pattern",
- "narrative": "string",
- "status": "active",
- "supportingExperienceIds": [
- "497f6eca-6276-4993-bfeb-53cbbbba6f08"
], - "synthesizedAt": "2019-08-24T14:15:22Z",
- "theme": "mechanical_validation"
}
], - "theme": "mechanical_validation",
- "total": 0
}Return experiences most similar to the supplied goal.
Body shape matches MemoryRetrieveRequest. The response carries
hits sorted by descending similarity, plus the echoed query
and total_found so callers can paginate without re-derivation.
Agentcode (string) or Agentcode (null) (Agentcode) Optional filter to a specific agent. | |
| goal required | string (Goal) non-empty Natural-language description of the task. |
| limit | integer (Limit) [ 1 .. 50 ] Default: 5 Maximum number of experiences to return. |
Minsimilarity (number) or Minsimilarity (null) (Minsimilarity) Optional retrieval-confidence floor: drop hits whose cosine similarity is below this value. None = no floor. | |
Onlysuccess (boolean) or Onlysuccess (null) (Onlysuccess) True = success-only, False = failures-only, None = no filter. | |
Projectid (string) or Projectid (null) (Projectid) Optional project scope. |
{- "agentCode": "string",
- "goal": "string",
- "limit": 5,
- "minSimilarity": -1,
- "onlySuccess": true,
- "projectId": "5a8591dd-4039-49df-9202-96385ba3eff8"
}{- "hits": [
- {
- "agentCode": "string",
- "confidence": "verbatim",
- "durationSeconds": 0,
- "error": "string",
- "experienceId": "637f9a15-ea20-4597-990b-d67bd20db1c1",
- "importance": 0,
- "projectId": "5a8591dd-4039-49df-9202-96385ba3eff8",
- "rank": 0,
- "resultSummary": "string",
- "runId": "string",
- "similarity": -1,
- "stepId": "string",
- "success": true,
- "taskType": "string",
- "timestamp": "2019-08-24T14:15:22Z"
}
], - "query": "string",
- "totalFound": 0
}Semantic search over design-decision knowledge (MET-471).
Wraps MemoryClient.search_design_rationale: a typed convenience
over the L1 knowledge base that asks "why was X decided?" and
returns ranked hits keyed off KnowledgeType.DESIGN_DECISION.
Requires the gateway to have wired a knowledge_service on
app.state.knowledge_service; the 503 from _get_client
covers the case where the service hasn't initialised.
| limit | integer (Limit) [ 1 .. 50 ] Default: 5 Maximum number of hits to return. |
Projectid (string) or Projectid (null) (Projectid) Optional project scope. | |
| query required | string (Query) non-empty Natural-language query against design-decision knowledge. |
{- "limit": 5,
- "projectId": "5a8591dd-4039-49df-9202-96385ba3eff8",
- "query": "string"
}{- "hits": [
- {
- "chunkIndex": 0,
- "content": "string",
- "heading": "string",
- "knowledgeType": "string",
- "similarityScore": 0,
- "sourcePath": "string",
- "sourceWorkProductId": "c83b8d43-b0ff-4427-847b-87295ebb87a8",
- "totalChunks": 0
}
], - "query": "string",
- "totalFound": 0
}{- "projects": [
- {
- "agent_count": 0,
- "created_at": "string",
- "description": "string",
- "id": "string",
- "last_updated": "string",
- "name": "string",
- "status": "string",
- "work_products": [
- {
- "id": "string",
- "name": "string",
- "status": "string",
- "type": "string",
- "updated_at": "string"
}
]
}
], - "total": 0
}Create a new hardware project (starts with no work products).
| description | string (Description) <= 2000 characters Default: "" Project description |
| name required | string (Name) [ 1 .. 200 ] characters Project name |
| status | string (Status) Default: "draft" Initial project status |
{- "description": "",
- "name": "string",
- "status": "draft"
}{- "agent_count": 0,
- "created_at": "string",
- "description": "string",
- "id": "string",
- "last_updated": "string",
- "name": "string",
- "status": "string",
- "work_products": [
- {
- "id": "string",
- "name": "string",
- "status": "string",
- "type": "string",
- "updated_at": "string"
}
]
}Get a single project by ID.
| project_id required | string (Project Id) |
{- "agent_count": 0,
- "created_at": "string",
- "description": "string",
- "id": "string",
- "last_updated": "string",
- "name": "string",
- "status": "string",
- "work_products": [
- {
- "id": "string",
- "name": "string",
- "status": "string",
- "type": "string",
- "updated_at": "string"
}
]
}Rename, redescribe, or change the status of an existing project.
| project_id required | string (Project Id) |
Description (string) or Description (null) (Description) | |
Name (string) or Name (null) (Name) | |
Status (string) or Status (null) (Status) |
{- "description": "string",
- "name": "string",
- "status": "string"
}{- "agent_count": 0,
- "created_at": "string",
- "description": "string",
- "id": "string",
- "last_updated": "string",
- "name": "string",
- "status": "string",
- "work_products": [
- {
- "id": "string",
- "name": "string",
- "status": "string",
- "type": "string",
- "updated_at": "string"
}
]
}Remove a work-product link from a project (MET-484).
Lets callers clean up duplicate/stale work-product references without deleting the project. 404 if no matching link exists.
| project_id required | string (Project Id) |
| work_product_id required | string (Work Product Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}Promote (or veto), attributed to whoever made this request.
A dashboard click is a human act, so this route needs no separate approval hop the way the MCP path does — but the authority is still read off the request rather than the body (FORGE-393).
Comment (string) or Comment (null) (Comment) | |
| k | number (K) Default: 1 |
| level required | string (Level) |
| projectId required | string (Projectid) |
| reject | boolean (Reject) Default: false |
| requiredClaimIds required | Array of strings (Requiredclaimids) |
{- "comment": "string",
- "k": 1,
- "level": "string",
- "projectId": "string",
- "reject": false,
- "requiredClaimIds": [
- "string"
]
}{- "blockedReason": "string",
- "comment": "string",
- "decidedBy": "string",
- "gateId": "string",
- "level": "string",
- "promoted": true,
- "results": [
- {
- "decision": "string",
- "detail": "string",
- "requirementId": "string",
- "requirementName": "string",
- "waiverId": "string"
}
]
}{- "releases": [
- {
- "created_at": "string",
- "diff_from_previous": {
- "bom_delta": 0,
- "compared_to": "string",
- "decision_delta": 0,
- "evidence_delta": 0,
- "hierarchy_delta": 0
}, - "gate_status": "string",
- "node_id": "string",
- "snapshot": {
- "bom_item_ids": [
- "string"
], - "decision_ids": [
- "string"
], - "drawing_ids": [
- "string"
], - "evidence_ids": [
- "string"
], - "hierarchy_node_ids": [
- "string"
]
}, - "statement": "string",
- "title": "string"
}
]
}Notes (string) or Notes (null) (Notes) | |
| project_id required | string (Project Id) |
{- "notes": "string",
- "project_id": "string"
}{- "created_at": "string",
- "diff_from_previous": {
- "bom_delta": 0,
- "compared_to": "string",
- "decision_delta": 0,
- "evidence_delta": 0,
- "hierarchy_delta": 0
}, - "gate_status": "string",
- "node_id": "string",
- "snapshot": {
- "bom_item_ids": [
- "string"
], - "decision_ids": [
- "string"
], - "drawing_ids": [
- "string"
], - "evidence_ids": [
- "string"
], - "hierarchy_node_ids": [
- "string"
]
}, - "statement": "string",
- "title": "string"
}FORGE-259: create one structured constraint (metric/operator/limit/ unit/target_node_type) from the dashboard's constraint editor. Live pass/fail/no_data status for it then comes from the existing GET /v1/requirements/matrix, computed from real Claim/Evidence data -- this route only ever records the requirement's own declaration.
| expectedEvidence | string (Expectedevidence) Default: "" |
| limit required | number (Limit) |
| message | string (Message) Default: "" |
| metric required | string (Metric) |
| name required | string (Name) |
| operator | string (Operator) Default: "<=" |
| projectId required | string (Projectid) |
| severity | string (Severity) Default: "error" |
| targetNodeType | string (Targetnodetype) Default: "" |
| unit | string (Unit) Default: "" |
| verificationMethod | string (Verificationmethod) Default: "" |
{- "expectedEvidence": "",
- "limit": 0,
- "message": "",
- "metric": "string",
- "name": "string",
- "operator": "<=",
- "projectId": "string",
- "severity": "error",
- "targetNodeType": "",
- "unit": "",
- "verificationMethod": ""
}{- "constraintId": "string",
- "setWorkProductId": "string"
}FORGE-297 (gap G-I1): the 5 traceability coverage percentages
(needs->requirements, requirements->architecture, requirements->
verification, verification->evidence, critical_requirements->evidence),
computed live by TraceabilityAgent (FORGE-56/73) -- previously real,
tested code with no gateway route exposing it at all.
| project_id required | string (Project Id) |
{- "critical_requirements_to_evidence": 0,
- "needs_to_requirements": 0,
- "requirements_to_architecture": 0,
- "requirements_to_verification": 0,
- "verification_to_evidence": 0
}FORGE-318: requirements x claims x evidence, one row per real requirement -- status (pass/uncertain/fail/no_data/stale) derived live from current claim + evidence staleness state, never cached.
| project_id required | string (Project Id) |
{- "revisionRefs": [ ],
- "rows": [
- {
- "artefactIds": [
- "string"
], - "detail": "string",
- "evidence": [
- {
- "id": "string",
- "limit": 0,
- "margin": 0,
- "method": "string",
- "staleness": "string",
- "tier": 0,
- "value": 0
}
], - "expectedEvidence": "",
- "limitText": "string",
- "requirementId": "string",
- "requirementName": "string",
- "revisionRef": "string",
- "status": "string",
- "verificationMethod": ""
}
]
}| project_id required | string (Project Id) |
| product_type | string (Product Type) Default: "generic" |
{- "completeness": {
- "covered": [
- "string"
], - "missing": [
- "string"
], - "productType": "string"
}, - "conflicts": [
- {
- "aId": "string",
- "aName": "string",
- "bId": "string",
- "bName": "string",
- "detail": "string"
}
], - "requirements": [
- {
- "atomicity": "string",
- "clarity": "string",
- "conflicts": [
- "string"
], - "id": "string",
- "name": "string",
- "quantified": "string",
- "severity": "string",
- "text": "string",
- "traceability": "string",
- "verificationReady": "string"
}
]
}Generate a proposed rewrite for one flawed requirement -- diagnoses with the same deterministic linter the quality report already used, then asks the Requirement Author agent for a corrected version linked back to the original via REFINES. Never writes anything itself; the caller applies the resulting patch through the normal review path.
| requirement_id required | string (Requirement Id) |
{- "conclusions": [
- "string"
], - "proposedText": "string",
- "rationale": "string"
}required | Array of objects (Joints) |
required | Array of objects (Links) |
| payload_mass_kg | number (Payload Mass Kg) Default: 0 |
Array of Payload Position World Mm (items) or Payload Position World Mm (null) (Payload Position World Mm) |
{- "joints": [
- {
- "name": "string",
- "position_world_mm": [
- 0,
- 0,
- 0
]
}
], - "links": [
- {
- "com_world_mm": [
- 0,
- 0,
- 0
], - "mass_kg": 0,
- "name": "string"
}
], - "payload_mass_kg": 0,
- "payload_position_world_mm": [
- 0,
- 0,
- 0
]
}{- "loads": [
- {
- "joint_name": "string",
- "reaction_force_n": [
- 0,
- 0,
- 0
], - "reaction_moment_n_mm": [
- 0,
- 0,
- 0
], - "supported_mass_kg": 0
}
], - "worst_joint": {
- "joint_name": "string",
- "reaction_force_n": [
- 0,
- 0,
- 0
], - "reaction_moment_n_mm": [
- 0,
- 0,
- 0
], - "supported_mass_kg": 0
}
}Every run, or one project's.
Unfiltered before: /runs showed every project's work in one list, and
the project a run belonged to was only inside its request blob. Passing
project_id scopes it; the response says how many runs were left out
for having no project, so they do not simply vanish.
Project Id (string) or Project Id (null) (Project Id) |
{- "runs": [
- {
- "approval_deadline": 0,
- "approval_reason": "string",
- "approved_by": "string",
- "approver_verified": false,
- "created_at": 0,
- "engine": "string",
- "error": "string",
- "flow_content_hash": "string",
- "flow_version_id": "string",
- "history": [
- "string"
], - "id": "string",
- "project_id": "string",
- "request": { },
- "result": { },
- "status": "string",
- "updated_at": 0,
- "usage": { }
}
], - "unscoped_count": 0
}object (Request) Opaque run input (goal, spec, config) handed to the harness. | |
| start | boolean (Start) Default: true Transition queued -> running immediately after creation. |
{- "request": { },
- "start": true
}{- "approval_deadline": 0,
- "approval_reason": "string",
- "approved_by": "string",
- "approver_verified": false,
- "created_at": 0,
- "engine": "string",
- "error": "string",
- "flow_content_hash": "string",
- "flow_version_id": "string",
- "history": [
- "string"
], - "id": "string",
- "project_id": "string",
- "request": { },
- "result": { },
- "status": "string",
- "updated_at": 0,
- "usage": { }
}{- "approval_deadline": 0,
- "approval_reason": "string",
- "approved_by": "string",
- "approver_verified": false,
- "created_at": 0,
- "engine": "string",
- "error": "string",
- "flow_content_hash": "string",
- "flow_version_id": "string",
- "history": [
- "string"
], - "id": "string",
- "project_id": "string",
- "request": { },
- "result": { },
- "status": "string",
- "updated_at": 0,
- "usage": { }
}Answer the gate this run is parked at.
Async so the store transition — which resolves the in-process executor's gate future via the coordinator — runs on the event-loop thread (future resolution is not thread-safe from FastAPI's sync worker pool).
On Temporal the decision is also signalled into the workflow, which is what actually resumes it (FORGE-401). The store transition stays, because the run list, the SSE stream and the ledger all read from it.
The deciding human comes from the request, never the body (FORGE-393).
| run_id required | string (Run Id) |
| decision required | string (Decision) Enum: "approve" "reject" "retry" "rework" |
| reason | string (Reason) <= 2000 characters Default: "" |
| to_phase | string (To Phase) <= 200 characters Default: "" |
{- "decision": "approve",
- "reason": "",
- "to_phase": ""
}{- "approval_deadline": 0,
- "approval_reason": "string",
- "approved_by": "string",
- "approver_verified": false,
- "created_at": 0,
- "engine": "string",
- "error": "string",
- "flow_content_hash": "string",
- "flow_version_id": "string",
- "history": [
- "string"
], - "id": "string",
- "project_id": "string",
- "request": { },
- "result": { },
- "status": "string",
- "updated_at": 0,
- "usage": { }
}Phase-by-phase state of a design-flow run.
Queries the Temporal workflow. A workflow query is answered by a worker,
so with none running there is nobody to answer -- which is reported as
live: false with a reason rather than as an empty flow, because an
empty flow and a flow nobody can see render identically and mean opposite
things.
| run_id required | string (Run Id) |
{- "attempt": 1,
- "awaitingGate": "string",
- "currentPhase": "string",
- "detail": "",
- "error": "string",
- "events": [
- { }
], - "flow": "string",
- "flowContentHash": "string",
- "flowVersion": "string",
- "flowVersionId": "string",
- "gateFindings": [
- "string"
], - "gateReady": true,
- "live": true,
- "maxReworkCycles": 0,
- "phases": [
- {
- "artifacts": [
- "string"
], - "disciplines": [
- "string"
], - "gate": "string",
- "id": "string",
- "status": "string",
- "summary": "",
- "title": "string",
- "usage": { }
}
], - "retriesLeft": 0,
- "reworkCycles": 0,
- "reworksLeft": 0,
- "runId": "string",
- "status": "string",
- "usage": { }
}Record that a run's workflow has opened a gate (FORGE-489).
Called by the design-flow worker's announcer. The workflow is the
authority on whether a gate is open, so this first re-reads it
(_reconcile_run). Only when the workflow cannot be asked does it fall
back to the worker's word. Either way it moves the record to
awaiting_approval, which is what lists the run on the Approvals page
and publishes the change on its SSE stream. It never approves: a decision
still has to come through /approval, and the workflow ignores one for
a gate that is not open.
| run_id required | string (Run Id) |
| gate required | string (Gate) non-empty |
| reason | string (Reason) Default: "" |
{- "gate": "string",
- "reason": ""
}{- "approval_deadline": 0,
- "approval_reason": "string",
- "approved_by": "string",
- "approver_verified": false,
- "created_at": 0,
- "engine": "string",
- "error": "string",
- "flow_content_hash": "string",
- "flow_version_id": "string",
- "history": [
- "string"
], - "id": "string",
- "project_id": "string",
- "request": { },
- "result": { },
- "status": "string",
- "updated_at": 0,
- "usage": { }
}Plan a change to a running flow and hold it for a person.
Nothing changes until somebody approves the approval this returns, and
then POST /v1/runs/{run_id}/patches/{version_id}/apply applies it at
the run's next gate. Only the phases the change touches re-run.
| run_id required | string (Run Id) |
| expectedContentHash required | string (Expectedcontenthash) |
| invalidate | Array of strings (Invalidate) |
Array of objects (Operations) | |
| reason required | string (Reason) |
{- "expectedContentHash": "string",
- "invalidate": [
- "string"
], - "operations": [
- { }
], - "reason": "string"
}{- "added": [
- "string"
], - "approvalId": "string",
- "changes": [
- "string"
], - "nextStep": "string",
- "notes": [
- "string"
], - "preserved": [
- "string"
], - "removed": [
- "string"
], - "rerun": [
- "string"
], - "runId": "string",
- "versionId": "string"
}Apply an approved patch. Refused unless approved and still current.
| run_id required | string (Run Id) |
| version_id required | string (Version Id) |
{- "nextStep": "string",
- "rerun": [
- "string"
], - "runId": "string",
- "versionId": "string"
}List all agent sessions.
Merges internal Temporal WorkflowRuns with externally-recorded
agent sessions (MET-493) so MCP/CLI-driven work shows up alongside
autonomous runs. Most-recent-first.
project_id scopes to one project (MET-516). Internal Temporal runs
carry no project, so they're excluded when a project filter is set.
Project Id (string) or Project Id (null) (Project Id) |
{- "sessions": [
- {
- "agent_code": "string",
- "completed_at": "string",
- "events": [
- {
- "agent_code": "string",
- "data": { },
- "id": "string",
- "message": "string",
- "timestamp": "string",
- "type": "string"
}
], - "id": "string",
- "project_id": "string",
- "run_id": "string",
- "source": "string",
- "started_at": "string",
- "status": "string",
- "summary": "string",
- "task_type": "string"
}
], - "total": 0,
- "unscoped_count": 0
}Open a new externally-recorded agent session.
| agent_code required | string (Agent Code) |
Project Id (string) or Project Id (null) (Project Id) | |
| task_type required | string (Task Type) |
Title (string) or Title (null) (Title) |
{- "agent_code": "string",
- "project_id": "string",
- "task_type": "string",
- "title": "string"
}{- "agent_code": "string",
- "completed_at": "string",
- "events": [
- {
- "agent_code": "string",
- "data": { },
- "id": "string",
- "message": "string",
- "timestamp": "string",
- "type": "string"
}
], - "id": "string",
- "project_id": "string",
- "run_id": "string",
- "source": "string",
- "started_at": "string",
- "status": "string",
- "summary": "string",
- "task_type": "string"
}Get a single session by ID (store first, then workflow engine).
| session_id required | string (Session Id) |
{- "agent_code": "string",
- "completed_at": "string",
- "events": [
- {
- "agent_code": "string",
- "data": { },
- "id": "string",
- "message": "string",
- "timestamp": "string",
- "type": "string"
}
], - "id": "string",
- "project_id": "string",
- "run_id": "string",
- "source": "string",
- "started_at": "string",
- "status": "string",
- "summary": "string",
- "task_type": "string"
}Complete a session (set terminal status + optional summary).
| session_id required | string (Session Id) |
| status required | string (Status) |
Summary (string) or Summary (null) (Summary) |
{- "status": "string",
- "summary": "string"
}{- "agent_code": "string",
- "completed_at": "string",
- "events": [
- {
- "agent_code": "string",
- "data": { },
- "id": "string",
- "message": "string",
- "timestamp": "string",
- "type": "string"
}
], - "id": "string",
- "project_id": "string",
- "run_id": "string",
- "source": "string",
- "started_at": "string",
- "status": "string",
- "summary": "string",
- "task_type": "string"
}Append one event (thought / action / decision / …) to a session.
| session_id required | string (Session Id) |
object (Data) | |
| message required | string (Message) |
| type required | string (Type) |
{- "data": { },
- "message": "string",
- "type": "string"
}{- "event_id": "string",
- "seq": 0
}List load cases, optionally scoped to a project.
Empty (not a 404) when the project has none yet, matching /v1/bom.
Project Id (string) or Project Id (null) (Project Id) |
{- "loadCases": [
- {
- "createdAt": "string",
- "fixedNodeSet": "string",
- "id": "string",
- "loadForceN": [
- 0
], - "loadNodeSet": "string",
- "material": { },
- "name": "string",
- "projectId": "string",
- "sourceOfLoads": "string",
- "updatedAt": "string"
}
], - "total": 0
}Create a load case, via the same document-recorder path
twin.record_document(document_type='load_case') uses (see module
docstring) so a dashboard-authored and agent-authored case are
indistinguishable on the twin.
| fixedNodeSet required | string (Fixednodeset) non-empty |
| loadForceN required | Array of numbers (Loadforcen) = 3 items |
| loadNodeSet required | string (Loadnodeset) non-empty |
required | object (Material) non-empty |
| name required | string (Name) [ 1 .. 200 ] characters |
| projectId required | string (Projectid) non-empty |
Sourceofloads (string) or Sourceofloads (null) (Sourceofloads) | |
Array of Sourcepartnodeids (strings) or Sourcepartnodeids (null) (Sourcepartnodeids) |
{- "fixedNodeSet": "string",
- "loadForceN": [
- 0,
- 0,
- 0
], - "loadNodeSet": "string",
- "material": { },
- "name": "string",
- "projectId": "string",
- "sourceOfLoads": "string",
- "sourcePartNodeIds": [
- "string"
]
}{- "createdAt": "string",
- "fixedNodeSet": "string",
- "id": "string",
- "loadForceN": [
- 0
], - "loadNodeSet": "string",
- "material": { },
- "name": "string",
- "projectId": "string",
- "sourceOfLoads": "string",
- "updatedAt": "string"
}Named-face geometry for an already-generated mesh (FORGE-277).
Backs the dashboard's geometric boundary-condition face picker: given a
mesh file path (from an earlier freecad.generate_mesh call, e.g.
surfaced in a forge chat turn), returns each named surface group's real
centroid/normal/area/bbox so the dashboard can render pickable face
patches instead of a blind "type the gmsh group name" text field.
| meshFile required | string (Meshfile) non-empty |
{- "meshFile": "string"
}{- "faces": [
- {
- "areaMm2": 0,
- "bboxMm": {
- "property1": [
- 0
], - "property2": [
- 0
]
}, - "centroidMm": [
- 0
], - "name": "string",
- "normal": [
- 0
]
}
], - "meshFile": "string"
}List FEA results, optionally scoped to a project (FORGE-279).
Empty (not a 404) when the project has none yet, matching
list_load_cases. Creation is agent-driven only (see module
docstring) — there is no corresponding POST.
Project Id (string) or Project Id (null) (Project Id) |
{- "results": [
- {
- "analysedGeometry": { },
- "analysisType": "string",
- "createdAt": "string",
- "fieldQuantities": [
- "string"
], - "fieldRanges": { },
- "fieldSizeBytes": 0,
- "fixtures": [
- { }
], - "hasField": false,
- "id": "string",
- "loadCase": "string",
- "loadCaseSpec": { },
- "loads": [
- { }
], - "maxDisplacementMm": 0,
- "maxTemperatureC": 0,
- "maxVonMisesMpa": 0,
- "meshConvergence": { },
- "meshStats": { },
- "name": "string",
- "projectId": "string",
- "updatedAt": "string"
}
], - "total": 0
}Serve a simulation_result's 3D result field (FORGE-532).
404 field not stored for a result recorded without one (every
result before FORGE-532, or one whose MinIO write failed); the
dashboard treats that as "show the numbers only".
| result_id required | string (Result Id) |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}{- "drawings": [
- {
- "approved": true,
- "approved_at": "string",
- "approved_by": "string",
- "created_at": "string",
- "dimensions": [
- {
- "feature": "string",
- "nominal_mm": 0,
- "tolerance_minus_mm": 0,
- "tolerance_plus_mm": 0
}
], - "gdt_callouts": [
- {
- "datum_refs": [
- "string"
], - "feature": "string",
- "symbol": "string",
- "tolerance_value_mm": 0
}
], - "inspection_requirements": [
- "string"
], - "name": "string",
- "node_id": "string",
- "part_name": "string",
- "surface_finishes": [
- {
- "feature": "string",
- "ra_um": 0
}
]
}
]
}| project_id required | string (Project Id) |
{- "project_id": "string"
}{- "entries": [
- {
- "acceptance_value": "string",
- "node_id": "string",
- "requirement_id": "string",
- "step": "string"
}
], - "project_id": "string"
}required | object (Criteriascores) |
| evidenceBackedCriteria | Array of strings (Evidencebackedcriteria) Default: [] |
Projectid (string) or Projectid (null) (Projectid) | |
| title required | string (Title) |
{- "criteriaScores": {
- "property1": 0,
- "property2": 0
}, - "evidenceBackedCriteria": [ ],
- "projectId": "string",
- "title": "string"
}{ }| optionIds required | Array of strings (Optionids) |
Projectid (string) or Projectid (null) (Projectid) | |
| rationale required | string (Rationale) |
Array of Requirementids (strings) or Requirementids (null) (Requirementids) | |
| selectedOptionId required | string (Selectedoptionid) |
| title required | string (Title) |
required | object (Weights) |
{- "optionIds": [
- "string"
], - "projectId": "string",
- "rationale": "string",
- "requirementIds": [
- "string"
], - "selectedOptionId": "string",
- "title": "string",
- "weights": {
- "property1": 0,
- "property2": 0
}
}{ }A project's baselines, newest first.
Project Id (string) or Project Id (null) (Project Id) |
{- "baselines": [
- {
- "approved_by": [
- "string"
], - "created_at": "string",
- "gate_id": "string",
- "id": "string",
- "item_count": 0,
- "member_count": 0,
- "name": "string",
- "project_id": "string",
- "reason": "",
- "run_id": "string",
- "source": "manual"
}
], - "total": 0
}Per item: unchanged, changed (@x -> @y), added or removed between a and b.
| a required | string (A) Baseline id |
| b required | string (B) Baseline id, or 'current' for the project's current items |
{- "a": {
- "approved_by": [
- "string"
], - "created_at": "string",
- "gate_id": "string",
- "id": "string",
- "item_count": 0,
- "member_count": 0,
- "name": "string",
- "project_id": "string",
- "reason": "",
- "run_id": "string",
- "source": "manual"
}, - "b": {
- "approved_by": [
- "string"
], - "created_at": "string",
- "gate_id": "string",
- "id": "string",
- "item_count": 0,
- "member_count": 0,
- "name": "string",
- "project_id": "string",
- "reason": "",
- "run_id": "string",
- "source": "manual"
}, - "b_is_current": false,
- "counts": {
- "property1": 0,
- "property2": 0
}, - "items": [
- {
- "from_ref": "string",
- "from_revision": 0,
- "item_type": "string",
- "key": "string",
- "name": "string",
- "status": "string",
- "to_ref": "string",
- "to_revision": 0
}
]
}One baseline with its item pins and constraint/entity members.
| baseline_id required | string (Baseline Id) |
{- "approved_by": [
- "string"
], - "created_at": "string",
- "gate_id": "string",
- "id": "string",
- "item_count": 0,
- "items": [
- {
- "item_type": "string",
- "key": "string",
- "name": "",
- "node_id": "string",
- "ref": "string",
- "revision": 0
}
], - "member_count": 0,
- "members": [
- {
- "entity_id": "string",
- "entity_kind": "string",
- "revision": 0
}
], - "name": "string",
- "project_id": "string",
- "reason": "",
- "run_id": "string",
- "source": "manual"
}The project's current items, records, counts and readiness (current items only).
| project_id required | string (Project Id) |
{- "counts": { },
- "groups": [
- {
- "count": 0,
- "item_type": "string",
- "keys": [
- "string"
]
}
], - "items": [
- {
- "author": "string",
- "change_reason": "string",
- "drafts": [
- {
- "adopted": false,
- "author": "string",
- "change_reason": "string",
- "created_at": "string",
- "gate_id": "string",
- "name": "string",
- "node_id": "string",
- "revision": 0,
- "run_id": "string",
- "status": "string"
}
], - "evidence_count": 0,
- "evidence_state": "string",
- "gate_id": "string",
- "item_type": "string",
- "key": "string",
- "name": "string",
- "node_id": "string",
- "ref": "string",
- "revision": 0,
- "revision_count": 0,
- "revision_status": "string",
- "run_id": "string",
- "updated_at": "string",
- "validation_status": "string"
}
], - "latest_baseline": {
- "approved_by": [
- "string"
], - "created_at": "string",
- "gate_id": "string",
- "id": "string",
- "item_count": 0,
- "member_count": 0,
- "name": "string",
- "project_id": "string",
- "reason": "",
- "run_id": "string",
- "source": "manual"
}, - "other": [
- {
- "name": "string",
- "node_id": "string",
- "type": "string",
- "updated_at": "string",
- "validation_status": "string"
}
], - "project_id": "string",
- "readiness": 0,
- "records": [
- {
- "analysed": [
- {
- "current": true,
- "key": "string",
- "ref": "string",
- "revision": 0
}
], - "created_at": "string",
- "name": "string",
- "node_id": "string",
- "out_of_date": false,
- "record_type": "string",
- "staleness": "string"
}
]
}List a project's hierarchy nodes, each with its parent link and its own rolled-up mass/cost.
Empty (not a 404) when the project has none yet, matching /v1/bom.
Note: computes one rollup per node (each its own subtree walk) -- fine
at the scale a hand-built product hierarchy actually reaches, not
optimized for a tree of thousands of nodes.
Project Id (string) or Project Id (null) (Project Id) |
{- "malformedBudgets": [ ],
- "nodes": [
- {
- "cost": 0,
- "costBudget": 0,
- "costBudgetDiscipline": "string",
- "costBudgetOwner": "string",
- "costOverBudget": true,
- "dissipationW": 0,
- "drawAverageW": 0,
- "drawPeakW": 0,
- "id": "string",
- "instanceOfBomItemId": "string",
- "interfaces": [
- {
- "description": "",
- "interfaceType": "",
- "otherComponent": "string",
- "quantities": [
- {
- "limit": 0,
- "metric": "string",
- "op": "<=",
- "unit": "string"
}
]
}
], - "kind": "string",
- "massBudgetDiscipline": "string",
- "massBudgetKg": 0,
- "massBudgetOwner": "string",
- "massKg": 0,
- "massOverBudget": true,
- "name": "string",
- "outputW": 0,
- "parentId": "string",
- "placement": { },
- "quantity": 0,
- "realizedByWorkProductId": "string"
}
]
}"Replace placeholder with part": attach or replace node_id's
REALIZED_BY (a cad_model work product, e.g. from POST /v1/twin/import)
and/or INSTANCE_OF (a BOMItem) geometry -- the dashboard's own action
reuses the SAME bound callable twin.realize_hierarchy_node uses.
| node_id required | string (Node Id) |
Bomitemid (string) or Bomitemid (null) (Bomitemid) | |
Workproductid (string) or Workproductid (null) (Workproductid) |
{- "bomItemId": "string",
- "workProductId": "string"
}{- "instanceOfBomItemId": "string",
- "nodeId": "string",
- "realizedByWorkProductId": "string"
}Upload a design file and register it as a work product in the Twin.
Accepts STEP, IGES, KiCad (.kicad_sch, .kicad_pcb), and FreeCAD (.FCStd) files. Metadata is extracted automatically based on file type.
| description | string (Description) Default: "" Work product description |
Domain (string) or Domain (null) (Domain) Domain (mechanical, electronics) | |
| file required | string <application/octet-stream> (File) Design file to import |
Project Id (string) or Project Id (null) (Project Id) Project to link to | |
Wp Type (string) or Wp Type (null) (Wp Type) Work product type |
{- "content_hash": "string",
- "created_at": "string",
- "domain": "string",
- "file_path": "string",
- "format": "string",
- "id": "string",
- "metadata": { },
- "name": "string",
- "project_id": "string",
- "wp_type": "string"
}Real boolean-intersection clearance/interference check between two named parts' committed STEP geometry (FORGE-272, gap G-D4).
Deliberately a pairwise check on two caller-named parts, not an
all-pairs sweep across an entire assembly, and deliberately not an ISO
286 tolerance-grade/fit classification -- see
api_gateway.twin.interference_check's module docstring for why
both are out of scope here.
| work_product_id_a required | string (Work Product Id A) |
| work_product_id_b required | string (Work Product Id B) |
{- "interference_area_mm2": 0,
- "interference_volume_mm3": 0,
- "interferes": true,
- "work_product_id_a": "string",
- "work_product_id_b": "string"
}Versioned definitions, each at its current head.
Project Id (string) or Project Id (null) (Project Id) | |
Item Type (string) or Item Type (null) (Item Type) e.g. cad_model, constraint_set |
{- "items": [
- {
- "created_at": "2019-08-24T14:15:22Z",
- "draft_node_id": "string",
- "draft_ref": "string",
- "draft_revision": 0,
- "head_node_id": "string",
- "head_ref": "string",
- "head_revision": 0,
- "item_type": "string",
- "key": "string",
- "name": "string",
- "project_id": "string",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "total": 0
}Geometry delta, parameter, requirement and field changes between two revisions.
| key required | string (Key) |
A (string) or A (null) (A) Older revision, e.g. 2 or @2 | |
B (string) or B (null) (B) Newer revision; defaults to the current one | |
Project Id (string) or Project Id (null) (Project Id) |
{- "a": { },
- "a_ref": "string",
- "b": { },
- "b_ref": "string",
- "dependents": [
- { }
], - "fields": [
- { }
], - "geometry": { },
- "item_type": "string",
- "key": "string",
- "name": "string",
- "parameters": [
- { }
], - "requirements": [
- { }
], - "warnings": [
- "string"
]
}One prd prose revision (KEY or KEY@n) with the current requirements.
| key required | string (Key) |
Project Id (string) or Project Id (null) (Project Id) | |
Run Id (string) or Run Id (null) (Run Id) Read as this design-flow run: include its open drafts |
{- "markdown": "string",
- "project_id": "string",
- "prose_ref": "string",
- "refs": [
- "string"
], - "requirement_count": 0,
- "requirement_refs": [
- "string"
], - "sources": [
- {
- "item_type": "string",
- "name": "string",
- "node_id": "string",
- "ref": "string",
- "requirement_count": 0
}
], - "title": "string"
}Every revision of one item, oldest first. key may carry an @n suffix.
| key required | string (Key) |
Project Id (string) or Project Id (null) (Project Id) | |
Run Id (string) or Run Id (null) (Run Id) Read as this design-flow run: include its open drafts |
{- "current": {
- "node_id": "string",
- "ref": "string",
- "revision": 0
}, - "item": {
- "created_at": "2019-08-24T14:15:22Z",
- "draft_node_id": "string",
- "draft_ref": "string",
- "draft_revision": 0,
- "head_node_id": "string",
- "head_ref": "string",
- "head_revision": 0,
- "item_type": "string",
- "key": "string",
- "name": "string",
- "project_id": "string",
- "updated_at": "2019-08-24T14:15:22Z"
}, - "lessons": [ ],
- "revisions": [
- {
- "adopted": true,
- "author": "string",
- "baselines": [
- "string"
], - "change_reason": "string",
- "change_set": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "gate": "string",
- "is_head": true,
- "name": "string",
- "node_id": "string",
- "phase": "string",
- "revision": 0,
- "run_id": "string",
- "status": "committed",
- "status_reason": "string"
}
]
}List file links with live sync status, optionally scoped to a project.
A link has no project of its own; scoping (MET-517) keeps only links whose
linked work product belongs to project_id.
Project Id (string) or Project Id (null) (Project Id) |
[- {
- "created_at": "string",
- "last_synced_at": "string",
- "source_hash": "string",
- "source_path": "string",
- "sync_status": "string",
- "tool": "string",
- "watch": true,
- "work_product_id": "string"
}
]List work-product nodes in the Digital Twin.
project_id scopes the view to a single project (MET-491). Omitted
or empty returns every node (including unscoped legacy nodes) —
preserving the prior global behaviour. A specific project_id
returns only that project's nodes; unscoped nodes are excluded.
FORGE-525: a design-flow run's drafts are left out until its gate
approves them (and for good if it never does); include_drafts=true
lists them too.
Domain (string) or Domain (null) (Domain) | |
Project Id (string) or Project Id (null) (Project Id) | |
| include_drafts | boolean (Include Drafts) Default: false |
{- "nodes": [
- {
- "assembly": { },
- "assemblyParts": [
- { }
], - "dependsOn": [
- {
- "itemKey": "string",
- "itemRef": "string",
- "itemType": "",
- "nodeId": "string",
- "revision": 0
}
], - "domain": "string",
- "geometryParameters": { },
- "hasScript": false,
- "id": "string",
- "meshStats": { },
- "name": "string",
- "poses": {
- "property1": {
- "property1": 0,
- "property2": 0
}, - "property2": {
- "property1": 0,
- "property2": 0
}
}, - "projectId": "string",
- "properties": {
- "property1": "string",
- "property2": "string"
}, - "staleness": {
- "reason": "string",
- "staleFor": { },
- "status": "string",
- "supersededBy": "string"
}, - "status": "string",
- "technicalDrawing": { },
- "type": "string",
- "updatedAt": "string"
}
], - "total": 0
}Real CSG boolean-cut between two committed STEP work products (MET-612).
A direct-commit endpoint, not the twin.propose_change/apply HITL
pipeline — a human cutting their own open model is not meaningfully
different from clicking Save (same rationale as twin.record_document,
whose apply executor doesn't fit this action either). Drives the
containerized CadQuery adapter via the shared MCP bridge, then commits the
result through the geometry recorder with provenance edges to both inputs.
| cutter_node_id required | string (Cutter Node Id) non-empty |
| operation | string (Operation) Default: "subtract" Enum: "subtract" "union" "intersect" |
Result Name (string) or Result Name (null) (Result Name) | |
| target_node_id required | string (Target Node Id) non-empty |
{- "cutter_node_id": "string",
- "operation": "subtract",
- "result_name": "string",
- "target_node_id": "string"
}{- "node": {
- "assembly": { },
- "assemblyParts": [
- { }
], - "dependsOn": [
- {
- "itemKey": "string",
- "itemRef": "string",
- "itemType": "",
- "nodeId": "string",
- "revision": 0
}
], - "domain": "string",
- "geometryParameters": { },
- "hasScript": false,
- "id": "string",
- "meshStats": { },
- "name": "string",
- "poses": {
- "property1": {
- "property1": 0,
- "property2": 0
}, - "property2": {
- "property1": 0,
- "property2": 0
}
}, - "projectId": "string",
- "properties": {
- "property1": "string",
- "property2": "string"
}, - "staleness": {
- "reason": "string",
- "staleFor": { },
- "status": "string",
- "supersededBy": "string"
}, - "status": "string",
- "technicalDrawing": { },
- "type": "string",
- "updatedAt": "string"
}, - "operation": "string",
- "result_area_mm2": 0,
- "result_volume_mm3": 0
}Delete a work-product node, its project links, and its MinIO blob (MET-484).
Without cascade a delete that would orphan dependents returns 409.
Best-effort on the blob (a storage failure doesn't block the delete).
| node_id required | string (Node Id) |
| cascade | boolean (Cascade) Default: false Also delete dependents that would orphan |
{- "detail": [
- {
- "ctx": { },
- "input": null,
- "loc": [
- "string"
], - "msg": "string",
- "type": "string"
}
]
}Get a single work-product node by ID.
| node_id required | string (Node Id) |
{- "assembly": { },
- "assemblyParts": [
- { }
], - "dependsOn": [
- {
- "itemKey": "string",
- "itemRef": "string",
- "itemType": "",
- "nodeId": "string",
- "revision": 0
}
], - "domain": "string",
- "geometryParameters": { },
- "hasScript": false,
- "id": "string",
- "meshStats": { },
- "name": "string",
- "poses": {
- "property1": {
- "property1": 0,
- "property2": 0
}, - "property2": {
- "property1": 0,
- "property2": 0
}
}, - "projectId": "string",
- "properties": {
- "property1": "string",
- "property2": "string"
}, - "staleness": {
- "reason": "string",
- "staleFor": { },
- "status": "string",
- "supersededBy": "string"
}, - "status": "string",
- "technicalDrawing": { },
- "type": "string",
- "updatedAt": "string"
}Human sign-off on a design_sketch work product (follow-up to MET-740/747).
The forge/agent side creates a sketch as unapproved (twin.commit_design_sketch); this is the dashboard's side of the gate — a human explicitly approving it before the calling agent is expected to proceed to real CAD/build work.
| node_id required | string <uuid> (Node Id) |
Approved By (string) or Approved By (null) (Approved By) Identifier of the human approving this sketch. | |
Any of string (Approved By) Identifier of the human approving this sketch. | |
{- "approved_by": "string"
}{- "approved": true,
- "approved_at": "string",
- "node_id": "string"
}Human sign-off on a technical_drawing work product (FORGE-293, gap G-H1).
Mirrors approve_design_sketch above -- a real work product, a real
approval gate, not a dashboard-local checkbox.
| node_id required | string <uuid> (Node Id) |
Approved By (string) or Approved By (null) (Approved By) Identifier of the human approving this drawing. | |
Any of string (Approved By) Identifier of the human approving this drawing. | |
{- "approved_by": "string"
}{- "approved": true,
- "approved_at": "string",
- "node_id": "string"
}Add/edit/delete mates+joints on an already-committed assembly node (FORGE-271).
Whole-list replace -- matches how the URDF/SDF/USD export panel's own
manual joint-list form already works (FORGE-245/MET-740). Persisted
directly onto metadata.assembly.joints: a joint is a logical
annotation, not new geometry, so this deliberately skips the full
VersionService revision machinery /nodes/{id}/iterate uses for
an actual re-export -- editing joints here never touches the underlying
committed blob.
Unlike the export panel, this does NOT require a live FreeCAD session or re-running an export -- it works on any already-committed node, any time, which is the whole point (FORGE-245's own fix only got joints persisted onto the node at commit time; this is what makes them editable afterward).
| node_id required | string <uuid> (Node Id) |
required | Array of objects (Joints) | ||||||||||||||
Array
| |||||||||||||||
{- "joints": [
- {
- "anchor": [
- 0,
- 0,
- 0
], - "axis": [
- 0,
- 0,
- 1
], - "base": "string",
- "follower": "string",
- "limits": {
- "property1": 0,
- "property2": 0
}, - "name": "string",
- "type": "string"
}
]
}{- "assembly": { },
- "nodeId": "string"
}Return a metadata diff between two revisions (1-indexed).
| node_id required | string <uuid> (Node Id) |
| v1 required | integer (V1) |
| v2 required | integer (V2) |
{- "added": { },
- "changed": {
- "property1": {
- "from_value": null,
- "to_value": null
}, - "property2": {
- "from_value": null,
- "to_value": null
}
}, - "removed": { },
- "revision_a": 0,
- "revision_b": 0,
- "work_product_id": "string"
}Stream a work product's stored file for download / open / preview.
download=false (default) returns the blob inline with a preview
content-type so the dashboard can render PDFs, images, text, and BOMs
in place; download=true forces a Content-Disposition: attachment.
| node_id required | string (Node Id) |
| download | boolean (Download) Default: false Force attachment download vs inline preview |
nullStream one of a work product's NAMED blobs (MET-740 follow-up).
GET /nodes/{id}/file only ever serves the primary blob. A
robot_description node stores N additional mesh blobs (one per
URDF link) under metadata["mesh_files"] (link filename -> MinIO
object key) — this resolves any of THOSE by filename, or falls back to
the primary blob if filename matches it, so the dashboard's
existing URDF preview (which expects every mesh reachable at
{some_base_url}/{filename}, exactly like a fresh export's
_cad_exports/{export_id}/ directory) can point straight at a
persisted node with zero re-export round trip.
| node_id required | string (Node Id) |
| filename required | string (Filename) |
nullDiff a work product's real geometry against its SUPERSEDES predecessor.
Unlike /diff above (a metadata-revision diff on the SAME node --
/iterate never changes the underlying blob, so no two revisions of one
node ever have different geometry), this walks the real SUPERSEDES edge a
re-committed, same-named CAD_MODEL gets (api_gateway.twin.geometry_ recorder) and compares the two NODES' actual STEP files.
| node_id required | string (Node Id) |
{- "area_delta_mm2": 0,
- "current_area_mm2": 0,
- "current_bounding_box": { },
- "current_volume_mm3": 0,
- "current_work_product_id": "string",
- "previous_area_mm2": 0,
- "previous_bounding_box": { },
- "previous_volume_mm3": 0,
- "previous_work_product_id": "string",
- "volume_delta_mm3": 0
}Record a new revision and apply metadata updates to a work product.
| node_id required | string <uuid> (Node Id) |
| change_description required | string (Change Description) |
object (Metadata Updates) Default: {} |
{- "change_description": "string",
- "metadata_updates": { }
}{- "change_description": "string",
- "content_hash": "string",
- "created_at": "string",
- "metadata_snapshot": { },
- "revision": 0
}Get the file link for a work product, with live sync status.
| node_id required | string (Node Id) |
{- "created_at": "string",
- "last_synced_at": "string",
- "source_hash": "string",
- "source_path": "string",
- "sync_status": "string",
- "tool": "string",
- "watch": true,
- "work_product_id": "string"
}Link a work product to an external source file.
The source file must exist on the gateway's filesystem. Once linked,
you can call POST /sync to re-import changes, or enable watch
for automatic detection.
| node_id required | string (Node Id) |
| source_path required | string (Source Path) Absolute path to the source file |
| tool | string (Tool) Default: "" Tool identifier (kicad, freecad, cadquery) |
| watch | boolean (Watch) Default: true Enable file watching |
{- "source_path": "string",
- "tool": "",
- "watch": true
}{- "created_at": "string",
- "last_synced_at": "string",
- "source_hash": "string",
- "source_path": "string",
- "sync_status": "string",
- "tool": "string",
- "watch": true,
- "work_product_id": "string"
}Convert a CAD work-product's STEP file to GLB and return the URL.
Reads the STEP file from the shared adapter workspace, converts it via the OCCT converter, and returns the GLB URL + metadata.
| node_id required | string (Node Id) |
| quality | string (Quality) ^(preview|standard|fine)$ Default: "standard" |
{ }The current git-versioned generation script for a CAD_MODEL node (MET-630).
Lets a dashboard parameter panel seed a regeneration proposal with the script as it stands today, rather than the user retyping it from scratch. 404 when the node has no linked script (imported geometry, or the git backend isn't configured).
| node_id required | string <uuid> (Node Id) |
{- "git_commit_sha": "string",
- "git_path": "string",
- "node_id": "string",
- "script_node_id": "string",
- "script_source": "string"
}Return the full revision history for a work product.
| node_id required | string <uuid> (Node Id) |
{- "revisions": [
- {
- "change_description": "string",
- "content_hash": "string",
- "created_at": "string",
- "metadata_snapshot": { },
- "revision": 0
}
], - "total": 0,
- "work_product_id": "string"
}Apply a reviewed plan. Requires approve: true; refused when the twin changed.
| project_id required | string (Project Id) |
| approve | boolean (Approve) Default: false Must be true: applying the plan writes to the twin. |
required | object (MigrationPlan-Input) The plan exactly as the dry run returned it. |
| reason | string (Reason) Default: "" Why this plan is approved (kept in the log). |
{- "approve": false,
- "plan": {
- "counts_after": { },
- "counts_before": { },
- "created_at": "string",
- "items": [
- {
- "confidence": "high",
- "existing_item": false,
- "head_node_id": "string",
- "head_revision": 0,
- "item_type": "string",
- "key": "string",
- "name": "string",
- "review_reasons": [
- "string"
], - "revisions": [
- {
- "created_at": "string",
- "evidence": "string",
- "existing": false,
- "name": "string",
- "node_id": "string",
- "revision": 0,
- "rule": "string",
- "run_id": "string",
- "status": "string",
- "status_reason": "string"
}
], - "rule": "string",
- "rules": [
- "string"
]
}
], - "low_confidence": [
- { }
], - "plan_hash": "",
- "project_id": "string",
- "records": [
- {
- "action": "string",
- "item_ref": "string",
- "item_type": "string",
- "name": "string",
- "node_id": "string",
- "pinned_node_id": "string",
- "reason": "string",
- "record_type": "string",
- "staleness": "string"
}
], - "skipped": [
- { }
], - "twin_fingerprint": "string"
}, - "reason": ""
}{- "approved_by": "string",
- "approver_verified": true,
- "result": {
- "applied": true,
- "by_type": {
- "property1": {
- "property1": 0,
- "property2": 0
}, - "property2": {
- "property1": 0,
- "property2": 0
}
}, - "failures": [
- {
- "property1": "string",
- "property2": "string"
}
], - "items_created": 0,
- "items_extended": 0,
- "plan_hash": "string",
- "project_id": "string",
- "records_pinned": 0,
- "records_stale": 0,
- "revisions_linked": 0,
- "run_summaries_marked": 0
}
}Dry run: how the project's legacy nodes would become items. Writes nothing.
| project_id required | string (Project Id) |
{- "empty": true,
- "plan": {
- "counts_after": { },
- "counts_before": { },
- "created_at": "string",
- "items": [
- {
- "confidence": "high",
- "existing_item": false,
- "head_node_id": "string",
- "head_revision": 0,
- "item_type": "string",
- "key": "string",
- "name": "string",
- "review_reasons": [
- "string"
], - "revisions": [
- {
- "created_at": "string",
- "evidence": "string",
- "existing": false,
- "name": "string",
- "node_id": "string",
- "revision": 0,
- "rule": "string",
- "run_id": "string",
- "status": "string",
- "status_reason": "string"
}
], - "rule": "string",
- "rules": [
- "string"
]
}
], - "low_confidence": [
- { }
], - "plan_hash": "",
- "project_id": "string",
- "records": [
- {
- "action": "string",
- "item_ref": "string",
- "item_type": "string",
- "name": "string",
- "node_id": "string",
- "pinned_node_id": "string",
- "reason": "string",
- "record_type": "string",
- "staleness": "string"
}
], - "skipped": [
- { }
], - "twin_fingerprint": "string"
}, - "report": "string"
}The project's prd, rendered from its current prose and requirements.
| project_id required | string (Project Id) |
Run Id (string) or Run Id (null) (Run Id) Read as this design-flow run: include its open drafts |
{- "markdown": "string",
- "project_id": "string",
- "prose_ref": "string",
- "refs": [
- "string"
], - "requirement_count": 0,
- "requirement_refs": [
- "string"
], - "sources": [
- {
- "item_type": "string",
- "name": "string",
- "node_id": "string",
- "ref": "string",
- "requirement_count": 0
}
], - "title": "string"
}List edges in the Digital Twin graph.
project_id scopes the view to a single project (MET-491), matching
list_twin_nodes. Omitted or empty returns every edge (including
unscoped legacy nodes) — preserving the prior global behaviour.
Project Id (string) or Project Id (null) (Project Id) |
{- "relationships": [
- {
- "id": "string",
- "label": "string",
- "sourceId": "string",
- "targetId": "string",
- "type": "string"
}
], - "total": 0
}Node id -> KEY@n for every revision (and constraint-set constraint) of the project.
| project_id required | string (Project Id) |
{- "nodes": {
- "property1": {
- "current": true,
- "item_type": "string",
- "key": "string",
- "ref": "string",
- "revision": 0,
- "revision_count": 0,
- "status": "string",
- "via": "string"
}, - "property2": {
- "current": true,
- "item_type": "string",
- "key": "string",
- "ref": "string",
- "revision": 0,
- "revision_count": 0,
- "status": "string",
- "via": "string"
}
}, - "project_id": "string"
}Revisions a design-flow run produced, and the baselines its gates recorded.
| run_id required | string (Run Id) |
Project Id (string) or Project Id (null) (Project Id) |
{- "baselines": [
- {
- "approved_by": [
- "string"
], - "created_at": "string",
- "gate_id": "string",
- "id": "string",
- "item_count": 0,
- "member_count": 0,
- "name": "string",
- "project_id": "string",
- "reason": "",
- "run_id": "string",
- "source": "manual"
}
], - "revisions": [
- {
- "baselined_by_gate": "string",
- "change_reason": "string",
- "created_at": "string",
- "is_current": true,
- "item_type": "string",
- "key": "string",
- "name": "string",
- "node_id": "string",
- "ref": "string",
- "revision": 0,
- "status": "string"
}
], - "run_id": "string"
}{- "joints": [
- {
- "base": "string",
- "cable_length_estimate_mm": 0,
- "follower": "string",
- "joint_name": "string",
- "joint_type": "string",
- "segment_length_mm": 0,
- "step_number": 0
}
], - "work_product_id": "string"
}