API documentation
Read and write AltoDocs documents programmatically — projects, documents, calculation canvases, and an MCP surface for AI agents.
AltoDocs API Documentation
REST API for AI agents to read and write structural engineering documents programmatically.
Base URL
https://europe-west1-statikdokai.cloudfunctions.net/api
Authentication
All endpoints (except GET /health) require a Bearer token:
Authorization: Bearer <API_KEY>
Generate an API key in the AltoDocs app under Settings → API Keys.
Content Model
Documents are structured as an array of blocks, each with a type and optional attrs and content:
json{ "type": "doc", "content": [ { "type": "heading", "attrs": { "level": 2, "nodeId": "uuid" }, "content": [{ "type": "text", "text": "Title" }] }, { "type": "paragraph", "attrs": { "nodeId": "uuid" }, "content": [{ "type": "text", "text": "Hello" }] }, { "type": "mathCanvas3", "attrs": { "nodeId": "uuid", "canvasData": { "..." }, "showGrid": true, "showBorder": true } } ] }
Common Block Types
| Type | Description |
|---|---|
heading | Section heading (attrs: level: 1–6) |
paragraph | Text paragraph |
mathCanvas3 | Calculation canvas (see MathCanvas Guide) |
beamAnalysis | Beam analysis with supports & loads |
screenshot | Image with annotations (see Screenshots Guide) |
bulletList / orderedList | Lists with listItem children |
Live Updates
Changes made via the API appear instantly in any open browser — no refresh needed. Cursor position is preserved.
Quick Start
bash# Health check curl https://europe-west1-statikdokai.cloudfunctions.net/api/health # List documents curl -H "Authorization: Bearer YOUR_KEY" \ https://europe-west1-statikdokai.cloudfunctions.net/api/documents # Get document content curl -H "Authorization: Bearer YOUR_KEY" \ https://europe-west1-statikdokai.cloudfunctions.net/api/documents/DOC_ID # Evaluate a calculation canvas and write results back curl -X POST -H "Authorization: Bearer YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"writeBack": true}' \ https://europe-west1-statikdokai.cloudfunctions.net/api/documents/DOC_ID/math-canvases/NODE_ID/evaluate # Change a region's display unit and precision curl -X PUT -H "Authorization: Bearer YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"unitMemory": {"preferredUnit": "mm", "precision": 0}}' \ https://europe-west1-statikdokai.cloudfunctions.net/api/documents/DOC_ID/math-canvases/NODE_ID/regions/REGION_ID
Further Reading
- Endpoints Reference — All endpoints with request/response formats
- MathCanvas Guide — Creating calculation canvases via API
- Screenshots Guide — Working with images and annotations via API
- MCP & AI Agents — Setup with Claude Desktop, VS Code, Cursor, and other MCP clients
- OpenClaw Skill — SKILL.md reference for OpenClaw users
API Endpoints Reference
Base URL: https://europe-west1-statikdokai.cloudfunctions.net/api
All endpoints require Authorization: Bearer <API_KEY> unless noted.
Health
GET /health
No auth required. Returns { "status": "ok" }.
Projects
GET /projects
List the authenticated user's projects.
Response:
json{ "count": 2, "projects": [{ "id": "abc", "sagensNavn": "Project Name", "..." }] }
POST /projects
Create a new project.
Body: { "sagensNavn": "My Project", "description": "..." }
Response: { "success": true, "id": "abc" }
GET /projects/:projectId/documents
List documents in a project.
Documents
GET /documents
List all user's documents. Optional query: ?limit=50
POST /documents
Create a new document with optional initial content.
Body:
json{ "title": "My Document", "projectId": "PROJECT_ID", "content": [ { "type": "heading", "attrs": { "level": 2 }, "content": [{ "type": "text", "text": "Title" }] }, { "type": "paragraph", "content": [{ "type": "text", "text": "Hello" }] } ] }
GET /documents/:id
Get full document content as JSON.
Response:
json{ "type": "doc", "content": [ { "type": "paragraph", "attrs": { "nodeId": "uuid" }, "content": [{ "type": "text", "text": "..." }] } ] }
GET /documents/:id/toc
Get a lightweight table of contents containing only heading nodes.
Optional query: ?maxLevel=3 to limit heading depth. Default is 3.
Response:
json{ "documentId": "DOC_ID", "maxLevel": 3, "count": 3, "headings": [ { "nodeId": "uuid-1", "level": 1, "path": "0", "text": "Chapter 1" }, { "nodeId": "uuid-2", "level": 2, "path": "4", "text": "Loads" } ] }
This endpoint is intended for AI agents and navigation tooling that need to find chapters without downloading or inspecting the full document body.
GET /documents/:id/summary
Get lightweight document metadata for agent planning.
Response:
json{ "documentId": "DOC_ID", "title": "Beam Design", "topLevelNodeCount": 42, "tocCount": 6, "totals": { "mathVariables": 14, "mathCanvases": 3, "beamAnalyses": 1, "beamAnalysesWithErrors": 1, "frameAnalyses": 0, "frameAnalysesWithErrors": 0, "screenshots": 2, "screenshotAnnotations": 7 }, "summary": "42 top-level nodes, 3 math canvases, 14 math variables, 1 beam analysis, 1 beam error.", "chapters": [ { "chapter": 1, "headingNodeId": "uuid-1", "title": "Loads", "summary": "12 top-level nodes, 1 math canvas, 1 beam analysis, 1 beam error.", "counts": { "beamAnalysesWithErrors": 1 } } ] }
This is designed for AI agents that need a semantic map of the document before reading any chapter content.
PUT /documents/:id
Replace entire document content.
Body:
json{ "content": [ { "type": "paragraph", "content": [{ "type": "text", "text": "New content" }] } ] }
Response: { "success": true, "message": "Replaced document with N block(s)" }
GET /documents/:id/nodes
List all blocks with their nodeIds and types.
Range mode is also available for chapter and section reads:
?afterNodeId=NODE_ID&beforeNodeId=NEXT_NODE_ID?startNodeId=NODE_ID&endNodeId=NEXT_NODE_ID?chapter=4
When range parameters are provided, the endpoint returns the matching top-level content blocks instead of the flattened node inventory.
GET /documents/:id/search?q=keyword
Search for text within the document.
query is accepted as an alias for q.
GET /documents/:id/nodes/:nodeId
Get a specific block by its nodeId.
POST /documents/:id/nodes
Insert new block(s) into the document.
Body:
json{ "position": "end", "content": [ { "type": "paragraph", "content": [{ "type": "text", "text": "Appended" }] } ] }
Position options: "end", "start", { "after": "nodeId" }, { "before": "nodeId" }
PUT /documents/:id/nodes/:nodeId
Update a specific block.
DELETE /documents/:id/nodes/:nodeId
Delete a specific block.
Calculation Canvases
See MathCanvas Guide for detailed format.
GET /documents/:id/math-canvases
List all calculation canvases in the document.
GET /documents/:id/math-canvases/:nodeId
Get normalized canvas data (regions, config).
GET /documents/:id/math-canvases/:nodeId/summary
Get semantic summary: defined variables, dependencies, expressions.
Response:
json{ "nodeId": "uuid", "regionCount": 5, "definitionCount": 3, "expressionCount": 2, "definitions": [ { "variable": "L", "regionId": "uuid", "expression": "L := 6 m" } ], "dependencies": { "M_max": ["q", "L"] }, "externalDependencies": ["q_ext"] }
PUT /documents/:id/math-canvases/:nodeId
Update canvas attributes (canvasData, showGrid, showBorder).
Body:
json{ "canvasData": { "regions": [ "..." ], "autoLayout": true }, "showGrid": true, "showBorder": true }
POST /documents/:id/math-canvases/:nodeId/regions
Add region(s) to an existing canvas.
Body:
json{ "regions": [ { "id": "uuid", "position": { "left": 0, "top": 0 }, "content": "\\placeholder[var]{F}\\coloneq\\placeholder[value]{10\\operatorname{\\textcolor{blue}{kN}}}", "type": "math" } ] }
PUT /documents/:id/math-canvases/:nodeId/regions/:regionId
Update a specific region's content, position, type, or display preferences.
Body (all fields optional — only provided fields are updated):
json{ "content": "\\placeholder[var]{L}\\coloneq\\placeholder[value]{9\\operatorname{\\textcolor{blue}{m}}}", "position": { "left": 0, "top": 120 }, "unitMemory": { "preferredUnit": "mm", "precision": 0, "numberFormat": "decimal" } }
| Field | Type | Description |
|---|---|---|
content | string | New LaTeX content |
position | object | { left, top } in pixels |
unitMemory | object | Display preferences (see MathCanvas Guide) |
unitMemory.preferredUnit | string | null | Convert result to this unit |
unitMemory.precision | number | Decimal places (default 2, use 0 for integer) |
unitMemory.numberFormat | string | "decimal", "engineering", or "percentage" |
DELETE /documents/:id/math-canvases/:nodeId/regions/:regionId
Delete a specific region from a canvas.
POST /documents/:id/math-canvases/:nodeId/evaluate
Evaluate all math regions server-side.
Body:
json{ "writeBack": true }
writeBack(boolean, defaultfalse) — whentrue, writes computed results back into the document (visible instantly in the browser)
Response:
json{ "success": true, "evaluated": 8, "errors": 0, "ok": true, "writeBack": true, "results": [ { "regionId": "def-L", "expression": "L := 6 m", "definedVar": "_L", "dependencies": [], "formattedLatex": "6.00 \\operatorname{...}{m}", "ok": true } ], "updatedRegions": [ "..." ] }
Note:
POST .../regionsandPUT .../regions/:regionIdalso auto-evaluate after writing. The response includes anevaluationfield with per-region results.
Screenshots
See Screenshots Guide for detailed annotation format.
GET /documents/:id/screenshots
List all screenshot nodes in the document.
GET /documents/:id/screenshots/:nodeId
Get screenshot metadata (image info, caption, alignment, annotation counts).
GET /documents/:id/screenshots/:nodeId/summary
Get extended summary with computed fields (total annotations, calibration status, image status).
PUT /documents/:id/screenshots/:nodeId
Update screenshot attributes (caption, alignment, dimensions, border, calibration).
Body (all fields optional):
json{ "caption": "Figure 3 — Foundation plan", "alignment": "left", "showBorder": false, "dimensionUnit": "cm", "calibration": { "px": 200, "mm": 1000, "scale": 5 } }
GET /documents/:id/screenshots/:nodeId/annotations
Get the full annotations data (lines, dimensions, rectangles, circles, textBoxes, leaders).
PUT /documents/:id/screenshots/:nodeId/annotations
Replace all annotations.
Body:
json{ "lines": [], "dimensions": [ { "id": "d1", "startX": 100, "startY": 300, "endX": 500, "endY": 300, "color": "#0000ff", "width": 2, "text": "2000 mm", "fontSize": 14, "auto": true } ], "rectangles": [], "circles": [], "textBoxes": [], "leaders": [] }
DELETE /documents/:id/screenshots/:nodeId/annotations
Clear all annotations.
PUT /documents/:id/screenshots/:nodeId/calibration
Set pixel-to-real-world calibration.
Body: { "px": 200, "mm": 1000, "scale": 5 }
DELETE /documents/:id/screenshots/:nodeId/calibration
Clear calibration.
Beam Analysis
GET /documents/:id/beam-analyses
List all beam analysis nodes in the document.
Response:
json{ "count": 1, "beamAnalyses": [ { "nodeId": "uuid", "type": "beamAnalysis", "beam_length": 4000, "beam_params": { "E": 210000, "I": 8360000 }, "units": { "length": "mm", "force": "kN", "distributed": "kN/m", "moment": "kN.m", "E": "MPa", "I": "mm4", "deflection": "mm", "stiffness": "kN/mm" }, "supportCount": 2, "loadCount": 1, "supports": [ { "id": "s1", "location": 0, "type": "pin" }, { "id": "s2", "location": 4000, "type": "roller" } ], "loads": [ { "id": "l1", "type": "UDLV", "magnitude": -10, "start_location": 0, "end_location": 4000 } ], "hasResults": true, "reactions": { "..." }, "analysis_data": { "..." } } ] }
GET /documents/:id/beam-analyses/:nodeId
Get a specific beam analysis node's config and results.
GET /documents/:id/beam-analyses/:nodeId/summary
Get detailed summary including support/load type breakdowns and load cases.
Response includes an extra summary field with:
supportTypes— count per type (e.g.{ "pin": 1, "roller": 1 })loadTypes— count per type (e.g.{ "UDLV": 1 })loadCases— list of distinct load case namesloadCaseCount— number of distinct load cases
PUT /documents/:id/beam-analyses/:nodeId
Update beam configuration. All fields optional — merges with current config.
Body:
json{ "beam_length": 6000, "beam_params": { "E": 210000, "I": 12000000 }, "units": { "length": "mm", "force": "kN" }, "supports": [ { "location": 0, "type": "fixed" }, { "location": 6000, "type": "roller" } ], "loads": [ { "type": "point", "location": 3000, "magnitude": -50 } ] }
Note: Providing
supportsorloadsarrays replaces the entire array. To add/remove individual items, use the sub-endpoints below.
POST /documents/:id/beam-analyses/:nodeId/supports
Add one or more supports.
Body:
json{ "supports": [ { "location": 2000, "type": "pin" } ] }
Or a single support: { "support": { "location": 2000, "type": "roller" } }
| Field | Type | Description |
|---|---|---|
location | number | Position along beam (in beam length units) |
type | string | "pin", "roller", "fixed", "moment_roller", or "y_spring" |
ky | number | Spring stiffness (only for y_spring type) |
id | string | Optional — auto-generated if omitted |
PUT /documents/:id/beam-analyses/:nodeId/supports/:supportId
Update a specific support's location, type, or stiffness.
Body (all fields optional):
json{ "location": 2500, "type": "roller" }
DELETE /documents/:id/beam-analyses/:nodeId/supports/:supportId
Delete a specific support.
POST /documents/:id/beam-analyses/:nodeId/loads
Add one or more loads.
Body:
json{ "loads": [ { "type": "point", "location": 2000, "magnitude": -25 } ] }
Or a single load: { "load": { "type": "moment", "location": 1000, "magnitude": 15 } }
| Field | Type | Description |
|---|---|---|
type | string | "point", "moment", "UDLV", or "TrapezoidalLoadV" |
location | number | Position (for point/moment loads) |
magnitude | number | Load value (negative = downward for vertical) |
start_location | number | Start position (for distributed loads) |
end_location | number | End position (for distributed loads) |
magnitude_end | number | End magnitude (for trapezoidal loads) |
loadCase | string | Optional load case name |
factor | number | Optional load factor (default 1.0) |
id | string | Optional — auto-generated if omitted |
PUT /documents/:id/beam-analyses/:nodeId/loads/:loadId
Update a specific load.
Body (all fields optional):
json{ "magnitude": -30, "location": 2500 }
DELETE /documents/:id/beam-analyses/:nodeId/loads/:loadId
Delete a specific load.
POST /documents/:id/beam-analyses/:nodeId/analyze
Run beam analysis by calling the Python backend. Returns reactions, internal forces, and deflection data.
Body:
json{ "writeBack": true }
writeBack(boolean, defaultfalse) — whentrue, stores the analysis results back into the document's Y.Doc (visible instantly in the browser)
Response:
json{ "success": true, "message": "Beam analysis completed", "writeBack": false, "reactions": { "support_at_0": { "type": "pin", "horizontal_reaction": 0, "vertical_reaction": 20, "moment_reaction": 0 }, "support_at_4000": { "type": "roller", "horizontal_reaction": 0, "vertical_reaction": 20, "moment_reaction": 0 } }, "analysis_data": { "shear_force": { "max": 20, "min": -20, "absmax": 20 }, "bending_moment": { "max": 20, "min": 0, "absmax": 20 }, "deflection": { "max": 0, "min": -18.99, "absmax": 18.99 } }, "hasPlots": true }
Note: The
/analyzeendpoint requires thebeam_pythonCloud Function to be deployed ineurope-west3. If it's not available, the endpoint returns a502error.
Frame Analysis
GET /documents/:id/frame-analyses
List all frame analysis nodes in the document.
Response:
json{ "count": 1, "frameAnalyses": [ { "nodeId": "uuid", "type": "frameAnalysis", "frameTitle": "Portal Frame", "EA": 21000, "EI": 8400, "elementCount": 3, "supportCount": 2, "pointLoadCount": 1, "qLoadCount": 1, "momentLoadCount": 1, "elements": [ { "id": "elem-1", "location": [[0,0],[0,4]] } ], "supports": [ { "id": "sup-1", "node_id": 1, "type": "fixed" } ], "point_loads": [ { "id": "pl-1", "node_id": 2, "Fx": 10, "Fy": 0 } ], "q_loads": [ { "id": "ql-1", "element_id": 2, "q": -5 } ], "moment_loads": [ { "id": "ml-1", "node_id": 3, "Ty": 15 } ], "hasResults": false, "status": null, "reactions": null } ] }
GET /documents/:id/frame-analyses/:nodeId
Get a specific frame analysis node's config and results.
GET /documents/:id/frame-analyses/:nodeId/summary
Get detailed summary including support type breakdown and total load count.
Response includes an extra summary field with:
supportTypes— count per type (e.g.{ "fixed": 2 })totalLoadCount— sum of point loads + distributed loads + moment loads
PUT /documents/:id/frame-analyses/:nodeId
Update frame configuration. All fields optional — merges with current config.
Body:
json{ "EA": 30000, "EI": 12000, "frameTitle": "Updated Frame", "elements": [ { "id": "elem-1", "location": [[0,0],[0,5]], "EA": 25000 } ], "supports": [ { "node_id": 1, "type": "fixed" } ], "point_loads": [ { "node_id": 2, "Fx": 10, "Fy": -5 } ], "q_loads": [ { "element_id": 1, "q": -8 } ], "moment_loads": [ { "node_id": 3, "Ty": 20 } ] }
Note: Providing
elements,supports,point_loads,q_loads, ormoment_loadsarrays replaces the entire array. To add/remove individual items, use the sub-endpoints below.
POST /documents/:id/frame-analyses/:nodeId/elements
Add one or more elements.
Body:
json{ "elements": [ { "id": "elem-4", "location": [[3,4],[3,0]], "EA": 25000 } ] }
Or a single element: { "element": { "location": [[3,4],[3,0]] } }
| Field | Type | Description |
|---|---|---|
location | number[][] | Two-point array [[x1,y1],[x2,y2]] defining the element |
EA | number | Optional axial stiffness override |
EI | number | Optional bending stiffness override |
id | string | Optional — auto-generated if omitted |
PUT /documents/:id/frame-analyses/:nodeId/elements/:elementId
Update a specific element's location or stiffness properties.
Body (all fields optional):
json{ "location": [[3,4],[3,1]], "EA": 30000 }
DELETE /documents/:id/frame-analyses/:nodeId/elements/:elementId
Delete a specific element.
POST /documents/:id/frame-analyses/:nodeId/supports
Add one or more supports.
Body:
json{ "supports": [ { "node_id": 3, "type": "hinged" } ] }
Or a single support: { "support": { "node_id": 3, "type": "fixed" } }
| Field | Type | Description |
|---|---|---|
node_id | number | Node index where the support is placed |
type | string | "fixed", "hinged", or "roll" |
direction | number | Optional direction angle (for roll supports) |
id | string | Optional — auto-generated if omitted |
PUT /documents/:id/frame-analyses/:nodeId/supports/:supportId
Update a specific support's type, node, or direction.
Body (all fields optional):
json{ "type": "roll", "direction": 90 }
DELETE /documents/:id/frame-analyses/:nodeId/supports/:supportId
Delete a specific support.
POST /documents/:id/frame-analyses/:nodeId/point-loads
Add one or more point loads.
Body:
json{ "point_loads": [ { "node_id": 2, "Fx": -5, "Fy": 20 } ] }
Or a single load: { "point_load": { "node_id": 2, "Fx": 10, "Fy": 0 } }
| Field | Type | Description |
|---|---|---|
node_id | number | Node index where the load is applied |
Fx | number | Horizontal force component |
Fy | number | Vertical force component |
rotation | number | Optional rotation angle |
id | string | Optional — auto-generated if omitted |
PUT /documents/:id/frame-analyses/:nodeId/point-loads/:loadId
Update a specific point load.
Body (all fields optional):
json{ "Fx": -10, "Fy": 25, "rotation": 45 }
DELETE /documents/:id/frame-analyses/:nodeId/point-loads/:loadId
Delete a specific point load.
POST /documents/:id/frame-analyses/:nodeId/q-loads
Add one or more distributed (q) loads.
Body:
json{ "q_loads": [ { "element_id": 1, "q": -8 } ] }
Or a single load: { "q_load": { "element_id": 2, "q": -5 } }
| Field | Type | Description |
|---|---|---|
element_id | number | Element index the load is applied to |
q | number | Distributed load magnitude (negative = downward) |
id | string | Optional — auto-generated if omitted |
PUT /documents/:id/frame-analyses/:nodeId/q-loads/:loadId
Update a specific distributed load.
Body (all fields optional):
json{ "q": -12, "element_id": 3 }
DELETE /documents/:id/frame-analyses/:nodeId/q-loads/:loadId
Delete a specific distributed load.
POST /documents/:id/frame-analyses/:nodeId/moment-loads
Add one or more moment loads.
Body:
json{ "moment_loads": [ { "node_id": 2, "Ty": -25 } ] }
Or a single load: { "moment_load": { "node_id": 3, "Ty": 15 } }
| Field | Type | Description |
|---|---|---|
node_id | number | Node index where the moment is applied |
Ty | number | Moment magnitude |
id | string | Optional — auto-generated if omitted |
PUT /documents/:id/frame-analyses/:nodeId/moment-loads/:loadId
Update a specific moment load.
Body (all fields optional):
json{ "Ty": -30, "node_id": 4 }
DELETE /documents/:id/frame-analyses/:nodeId/moment-loads/:loadId
Delete a specific moment load.
API Keys
POST /keys
Generate a new API key for the authenticated user.
Body: { "name": "My Agent Key" }
Response: { "key": "altodocs_...", "name": "My Agent Key" }
MathCanvas — API Guide
A MathCanvas is a live-evaluation calculation canvas embedded in your document. Each canvas contains regions — math expressions, text labels, or display-only LaTeX — that are evaluated top-to-bottom, left-to-right.
Canvas Structure
A calculation canvas in the document looks like:
json{ "type": "mathCanvas3", "attrs": { "canvasData": { "regions": [ "..." ], "autoLayout": true }, "showGrid": true, "showBorder": true } }
| Field | Type | Description |
|---|---|---|
canvasData.regions | array | The calculation regions (see below) |
canvasData.autoLayout | boolean | When true, the app auto-arranges regions vertically on first render, then clears the flag |
showGrid | boolean | Show background grid dots |
showBorder | boolean | Show canvas border |
Region Types
Math Region
json{ "id": "unique-id", "position": { "left": 0, "top": 0 }, "content": "\\placeholder[var]{L}\\coloneq\\placeholder[value]{6\\operatorname{\\textcolor{blue}{m}}}", "type": "math", "unitMemory": { "preferredUnit": "mm", "precision": 0, "numberFormat": "decimal" } }
| Field | Type | Default | Description |
|---|---|---|---|
unitMemory.preferredUnit | string | null | null | Convert result to this unit (e.g. "mm", "kN", "m^2") |
unitMemory.precision | number | 2 | Number of decimal places (0 = integer display) |
unitMemory.numberFormat | string | "decimal" | "decimal", "engineering", or "percentage" |
unitMemory.engineeringPower | number | 3 | Exponent for engineering notation (e.g. 3 = x10 cubed) |
Display preferences apply to results. For pure definitions (
var+value), settingpreferredUnitor customprecisioncauses the evaluator to append a converted result — e.g.,A_s := 2513 mm squared = 0.00251 m squared. Definitions withoutunitMemorystay clean.
Text Region
json{ "id": "unique-id", "position": { "left": 0, "top": 0 }, "content": "<b>Input Parameters</b>", "type": "text", "boundingBox": { "width": 300, "height": 36 } }
LaTeX Region
json{ "id": "unique-id", "position": { "left": 0, "top": 0 }, "content": "\\sigma = \\frac{M}{W}", "type": "latex" }
Placeholder System (Math Regions)
Math content uses a semantic placeholder system with exactly 4 valid types:
| Type | Purpose | Example |
|---|---|---|
var | Variable name being defined | \placeholder[var]{L} |
value | Numeric value (with optional unit) | \placeholder[value]{6\operatorname{\textcolor{blue}{m}}} |
expr | Expression referencing other variables | \placeholder[expr]{q \cdot L^2 / 8} |
result | Computed result (auto-filled by the system) | \placeholder[result]{} |
WARNING: Only
var,value,expr,resultare valid. Any other type (e.g.unit,resultValue) will be rejected and the region will render as empty red squares.
Assignment Operator
Use \coloneq (renders as :=). Do NOT use := directly.
Building Expressions
Variable Definition (constant value)
Define a variable with a fixed numeric value:
\placeholder[var]{VARNAME}\coloneq\placeholder[value]{NUMBER UNIT}
Examples:
\placeholder[var]{L}\coloneq\placeholder[value]{6\operatorname{\textcolor{blue}{m}}}
\placeholder[var]{q}\coloneq\placeholder[value]{15\frac{\operatorname{\textcolor{blue}{kN}}}{\operatorname{\textcolor{blue}{m}}}}
\placeholder[var]{f_{ck}}\coloneq\placeholder[value]{25\operatorname{\textcolor{blue}{MPa}}}
Variable Definition with Computed Expression
Define a variable whose value is computed from other variables. Leave \placeholder[result]{} empty — the system evaluates it automatically:
\placeholder[var]{VARNAME}\coloneq\placeholder[expr]{EXPRESSION}=\placeholder[result]{}
Examples:
\placeholder[var]{M_{max}}\coloneq\placeholder[expr]{\frac{q\cdot L^{2}}{8}}=\placeholder[result]{}
\placeholder[var]{A_s}\coloneq\placeholder[expr]{\frac{M_{max}}{f_{ck}\cdot d}}=\placeholder[result]{}
CRITICAL: The
=\placeholder[result]{}at the end is what triggers the system to compute and display the answer. Without it, nothing is evaluated. Leave the result empty — never compute it yourself.
Bare Expression (no variable name)
Evaluate an expression without assigning it to a variable:
\placeholder[expr]{0.9\cdot\left(g_{b,1}+g_{b,2}\right)}=\placeholder[result]{}
Math Operations in LaTeX
| Operation | LaTeX | Example |
|---|---|---|
| Multiplication | \cdot | q\cdot L |
| Division (fraction) | \frac{a}{b} | \frac{q\cdot L^{2}}{8} |
| Power | ^{n} | L^{2}, b^{3} |
| Square root | \sqrt{x} | \sqrt{A} |
| Parentheses | \left( \right) | \left(a+b\right) |
| Subscript | _{sub} | M_{max}, f_{ck} |
| Greek letters | \alpha, \sigma, etc. | \sigma_{s} |
| Pi | \pi | \pi\cdot d^{2} |
Units
Units are embedded inline inside value or result placeholders — they are NOT separate placeholders.
Format
\operatorname{\textcolor{blue}{UNIT}}
Examples
| Unit | LaTeX |
|---|---|
| kN | \operatorname{\textcolor{blue}{kN}} |
| m | \operatorname{\textcolor{blue}{m}} |
| mm | \operatorname{\textcolor{blue}{mm}} |
| MPa | \operatorname{\textcolor{blue}{MPa}} |
| kN/m squared | \frac{\operatorname{\textcolor{blue}{kN}}}{\operatorname{\textcolor{blue}{m}}^2} |
| kN/m | \frac{\operatorname{\textcolor{blue}{kN}}}{\operatorname{\textcolor{blue}{m}}} |
| mm to the 4th | \operatorname{\textcolor{blue}{mm}}^{4} |
| kN times m | \operatorname{\textcolor{blue}{kN}}\cdot\operatorname{\textcolor{blue}{m}} |
Display Preferences (Unit Conversion & Formatting)
Each region can have a unitMemory object that controls how the evaluated result is displayed. This enables:
- Unit conversion — show a result in a different (compatible) unit
- Precision control — set the number of decimal places (including 0 for integers)
- Number format — decimal, engineering notation, or percentage
Example: Convert and reformat via API
bash# Change delta_max to display in mm with 0 decimal places curl -X PUT "$BASE/documents/$DOC/math-canvases/$CANVAS/regions/calc-delta" \ -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d '{ "unitMemory": { "preferredUnit": "mm", "precision": 0 } }' # Change N_Rd to display in kN with 2 decimal places curl -X PUT "$BASE/documents/$DOC/math-canvases/$CANVAS/regions/calc-NRd" \ -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d '{ "unitMemory": { "preferredUnit": "kN", "precision": 2 } }' # Re-evaluate canvas to apply the new display preferences curl -X POST "$BASE/documents/$DOC/math-canvases/$CANVAS/evaluate" \ -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d '{ "writeBack": true }'
Supported Units
| Category | Units |
|---|---|
| Length | mm, cm, m, km |
| Force | N, kN, MN |
| Pressure | Pa, kPa, MPa, GPa |
| Mass | kg, ton |
| Time | second, minute, hour |
| Angles | deg, rad |
Compound units work too: m^2, kN/m, kN/m^2, cm^4, etc.
When results are written
| Region type | Has unitMemory? | Result written? |
|---|---|---|
Expression (\placeholder[expr]) | Any | Always |
Pure definition (\placeholder[value]) | No | Never (no redundant re-statement) |
Pure definition (\placeholder[value]) | Yes (preferredUnit or non-default precision) | Yes (unit conversion / reformatting) |
Live sync
Changes made via the API appear instantly in any open browser — no refresh needed.
Layout & Positioning (2D Canvas)
The canvas is a 2D coordinate space. Each region has:
position: { left, top }— top-left corner in pixelsboundingBox: { width, height }— rendered size in pixels (measured by the app)
Regions are positioned absolutely. You control where every region sits.
Approach 1: Auto-Layout (simple, for new canvases)
Set autoLayout: true and all positions to (0, 0). The app stacks them vertically.
Approach 2: Read-then-Position (precise control)
- Create the canvas (positions can be
(0, 0)) - Wait for the app to render (browser must load the document)
- Read via
GET /math-canvases/:nodeId— each region now hasboundingBox - Calculate positions from the known dimensions
- Update via
PUT /math-canvases/:nodeId
pythony = 0 for region in regions: h = region["boundingBox"]["height"] region["position"] = {"left": 0, "top": round(y)} y += h + 1 # 1px gap (or 0 for flush)
Position Affects Evaluation Order
Regions are evaluated top-to-bottom (by Y), then left-to-right (by X). A variable must be defined ABOVE (lower Y) the expression that uses it. If you move an expression above its dependencies, it will show a red ? error.
Evaluation
Server-Side Evaluation
The API includes a server-side evaluation engine that uses the same math engine as the browser. This enables:
- AI-driven computation — define variables and get results back immediately without a browser
- Batch evaluation — evaluate entire canvases in one API call
- Consistent results — server and browser always produce the same answers
Auto-Evaluate on Write
When you add or update regions via POST .../regions or PUT .../regions/:regionId, the server automatically evaluates all math regions and includes results in the response:
json{ "success": true, "regions": [ "..." ], "evaluation": { "evaluated": 4, "errors": 0, "results": [ { "regionId": "calc-M", "definedVar": "_M_max", "formattedLatex": "54.00 \\operatorname{...}", "ok": true } ] } }
The auto-evaluate also writes computed results into the document, so results appear instantly in the browser.
Explicit Evaluate Endpoint
POST /documents/:id/math-canvases/:nodeId/evaluate
Body:
json{ "writeBack": true }
| Field | Type | Default | Description |
|---|---|---|---|
writeBack | boolean | false | When true, writes computed results back into the document |
Response:
json{ "success": true, "evaluated": 8, "errors": 0, "ok": true, "writeBack": true, "results": [ { "regionId": "def-L", "expression": "L := 6 m", "definedVar": "_L", "dependencies": [], "formattedLatex": "6.00 \\operatorname{\\textcolor{blue}{m}}", "ok": true } ], "updatedRegions": [ "..." ] }
Browser-Side Evaluation
When content changes reach the browser (via live sync), the browser also evaluates independently. Since both server and browser use the same math engine, results always match.
Evaluation Order (detailed)
- Document position (earlier blocks in the document come first)
- Within a canvas: Y position (top-to-bottom)
- Same Y: X position (left-to-right)
Variables defined in earlier canvases are available in later ones.
Full Working Example
json{ "type": "mathCanvas3", "attrs": { "canvasData": { "autoLayout": true, "regions": [ { "id": "title-1", "position": { "left": 0, "top": 0 }, "content": "<b>Beam Calculation</b>", "type": "text", "boundingBox": { "width": 300, "height": 36 } }, { "id": "def-L", "position": { "left": 0, "top": 0 }, "content": "\\placeholder[var]{L}\\coloneq\\placeholder[value]{6\\operatorname{\\textcolor{blue}{m}}}", "type": "math" }, { "id": "def-q", "position": { "left": 0, "top": 0 }, "content": "\\placeholder[var]{q}\\coloneq\\placeholder[value]{10\\frac{\\operatorname{\\textcolor{blue}{kN}}}{\\operatorname{\\textcolor{blue}{m}}}}", "type": "math" }, { "id": "calc-M", "position": { "left": 0, "top": 0 }, "content": "\\placeholder[var]{M_{max}}\\coloneq\\placeholder[expr]{\\frac{q\\cdot L^{2}}{8}}=\\placeholder[result]{}", "type": "math" } ] }, "showGrid": true, "showBorder": true } }
This creates a canvas with:
- A bold title "Beam Calculation"
- L := 6 m
- q := 10 kN/m
- M_max := q times L squared / 8 = ? (auto-evaluated)
All regions are auto-arranged vertically on render.
MCP & AI Agents
There are two ways to connect AI assistants to your AltoDocs documents:
| Approach | For | How it works |
|---|---|---|
| MCP server | Code editors (VS Code Copilot, Claude Desktop, Cursor) | Wraps the REST API into Model Context Protocol tools — the editor calls the API on your behalf |
| OpenClaw Skill | OpenClaw | A SKILL.md file that teaches the assistant how to call the AltoDocs REST API directly — no MCP needed |
MCP Server (Code Editors)
The MCP server wraps the REST API so code-editor agents can list documents, create calculations, evaluate formulas, and write results — all via natural language.
Add this to your editor's MCP config:
json{ "servers": { "altodocs": { "type": "stdio", "command": "npx", "args": ["altodocs-mcp"], "env": { "ALTODOCS_API_KEY": "your_api_key_here" } } } }
Get your API key
- Open the AltoDocs app
- Go to Settings → API Keys
- Click Generate and copy the key (starts with
altodocs_) - Paste it into the config above
Client-specific setup
VS Code (GitHub Copilot)
Create .vscode/mcp.json in your workspace:
json{ "servers": { "altodocs": { "type": "stdio", "command": "npx", "args": ["altodocs-mcp"], "env": { "ALTODOCS_API_KEY": "your_api_key_here" } } } }
Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
json{ "mcpServers": { "altodocs": { "command": "npx", "args": ["altodocs-mcp"], "env": { "ALTODOCS_API_KEY": "your_api_key_here" } } } }
Available MCP Tools
Once connected, the AI agent has access to these tools:
Documents
| Tool | Description |
|---|---|
list_documents | List your documents (optionally filter by project) |
get_document | Read full document content as JSON |
search_document | Search for text within a document |
replace_document | Replace entire document content |
Blocks
| Tool | Description |
|---|---|
list_nodes | List all blocks with IDs, types, and text previews |
get_node | Get a specific block by ID |
insert_content | Insert new blocks at a position |
update_node | Update a block's text or attributes |
delete_node | Delete a block |
Calculation Canvases
| Tool | Description |
|---|---|
list_math_canvases | List all calculation canvases in a document |
get_math_canvas | Get canvas data with all regions |
summarize_math_canvas | Get variables, dependencies, and expressions |
update_math_canvas | Update canvas settings or replace all regions |
add_math_canvas_regions | Add new math/text/LaTeX regions |
update_math_canvas_region | Update a single region (content, position, units) |
delete_math_canvas_region | Delete a region |
evaluate_math_canvas | Evaluate all formulas and optionally write results back |
Beam Analyses
| Tool | Description |
|---|---|
list_beam_analyses | List all beam analysis nodes in a document |
get_beam_analysis | Get beam config, supports, loads, and results |
summarize_beam_analysis | Get detailed breakdown (support/load types, load cases) |
update_beam_analysis | Update beam config (length, params, units, supports, loads) |
add_beam_supports | Add supports to a beam |
update_beam_support | Update a specific support |
delete_beam_support | Delete a support |
add_beam_loads | Add loads to a beam |
update_beam_load | Update a specific load |
delete_beam_load | Delete a load |
run_beam_analysis | Run analysis and get reactions/forces/deflection |
Screenshots
| Tool | Description |
|---|---|
list_screenshots | List all screenshot nodes in a document |
get_screenshot | Get screenshot metadata (caption, alignment, annotation counts) |
summarize_screenshot | Get extended summary (calibration status, image status, totals) |
update_screenshot | Update caption, alignment, border, dimension settings |
get_screenshot_annotations | Get full annotation data (lines, dimensions, shapes, etc.) |
update_screenshot_annotations | Replace all annotations |
clear_screenshot_annotations | Remove all annotations |
set_screenshot_calibration | Set pixel-to-real-world calibration |
clear_screenshot_calibration | Remove calibration |
OpenClaw Skill (no MCP needed)
OpenClaw does not use MCP. Instead it uses Skills — a SKILL.md file that teaches the assistant about the AltoDocs API, calculation syntax, and unit handling. The assistant then calls the REST API directly.
Quick install
Download SKILL.md from Settings → API Keys (the SKILL.md button next to your key) and place it in your OpenClaw skills folder:
~/.openclaw/skills/altodocs/SKILL.md
Or use the terminal:
bashmkdir -p ~/.openclaw/skills/altodocs curl -o ~/.openclaw/skills/altodocs/SKILL.md \ https://statikdokai.web.app/downloads/altodocs-openclaw-skill/SKILL.md
The skill teaches your OpenClaw assistant:
- How to create and evaluate engineering calculations
- The placeholder syntax for math expressions
- Unit handling and conversion
- Layout and positioning of canvas regions
- Beam analysis configuration (supports, loads, running analysis)
- Screenshot annotation management (lines, dimensions, rectangles, calibration)
Full reference: See OpenClaw Skill for the complete guide — file structure, frontmatter fields, capabilities breakdown, and example conversations.
Example Conversation
Once connected (via MCP or the OpenClaw skill), you can talk to your AI assistant naturally:
You: Create a beam calculation with L = 6 m, q = 10 kN/m, and compute M_max = q*L^2/8
Agent: I'll create a calculation canvas in your document with those parameters... (uses
add_math_canvas_regions+evaluate_math_canvas)Result: M_max = 45.00 kN·m
You: Show delta_max in mm with no decimal places
Agent: I'll update the display settings for delta_max... (uses
update_math_canvas_regionwithunitMemory)Result: delta_max = 57 mm
Environment Variables
| Variable | Required | Description |
|---|---|---|
ALTODOCS_API_KEY | Yes | Your API key (generate in Settings → API Keys) |
ALTODOCS_API_URL | No | Custom API base URL (defaults to production) |
Full SKILL.md Reference
Below is the complete SKILL.md file. You can copy this directly instead of downloading:
<details> <summary><strong>Click to expand full SKILL.md</strong></summary></details>markdown--- name: altodocs description: Control AltoDocs engineering documents — create calculations, evaluate structural formulas, manage beam analyses, annotate images, and read/write document content via the AltoDocs REST API. metadata: { "openclaw": { "emoji": "📐", "requires": { "env": ["ALTODOCS_API_KEY"] }, "primaryEnv": "ALTODOCS_API_KEY", "homepage": "https://altodocs.io" } } --- # AltoDocs — Engineering Document API You have access to the AltoDocs API for creating and managing structural engineering documents with live calculation canvases, beam analyses, and annotated images. ## API Base URL ``` https://europe-west1-statikdokai.cloudfunctions.net/api ``` All requests need `Authorization: Bearer $ALTODOCS_API_KEY`. ## Core Workflow 1. **List documents**: `GET /documents` 2. **Get a document**: `GET /documents/:id` — returns JSON with all blocks (headings, paragraphs, calculation canvases, beam analyses, screenshots) 3. **Create calculations**: `POST /documents/:id/math-canvases/:nodeId/regions` — add math regions to a canvas 4. **Evaluate**: `POST /documents/:id/math-canvases/:nodeId/evaluate` with `{"writeBack": true}` — computes all formulas and writes results back 5. **Change units/precision**: `PUT /documents/:id/math-canvases/:nodeId/regions/:regionId` with `{"unitMemory": {"preferredUnit": "mm", "precision": 0}}` ## Available Endpoints ### Documents & Blocks | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/documents` | List all documents | | POST | `/documents` | Create a document | | GET | `/documents/:id` | Get document as JSON | | PUT | `/documents/:id` | Replace entire document | | GET | `/documents/:id/search?q=keyword` | Search text | | GET | `/documents/:id/nodes` | List all blocks | | GET | `/documents/:id/nodes/:nodeId` | Get a block | | POST | `/documents/:id/nodes` | Insert blocks | | PUT | `/documents/:id/nodes/:nodeId` | Update a block | | DELETE | `/documents/:id/nodes/:nodeId` | Delete a block | ### Calculation Canvases (mathCanvas3) | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/documents/:id/math-canvases` | List canvases | | GET | `/documents/:id/math-canvases/:nodeId` | Get canvas data | | GET | `/documents/:id/math-canvases/:nodeId/summary` | Get variables, dependencies | | PUT | `/documents/:id/math-canvases/:nodeId` | Update canvas attrs | | POST | `/documents/:id/math-canvases/:nodeId/regions` | Add regions | | PUT | `/documents/:id/math-canvases/:nodeId/regions/:regionId` | Update a region | | DELETE | `/documents/:id/math-canvases/:nodeId/regions/:regionId` | Delete a region | | POST | `/documents/:id/math-canvases/:nodeId/evaluate` | Evaluate all math | ### Beam Analyses (beamAnalysis) | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/documents/:id/beam-analyses` | List beam analysis nodes | | GET | `/documents/:id/beam-analyses/:nodeId` | Get beam config & results | | GET | `/documents/:id/beam-analyses/:nodeId/summary` | Get detailed breakdown | | PUT | `/documents/:id/beam-analyses/:nodeId` | Update beam config | | POST | `/documents/:id/beam-analyses/:nodeId/supports` | Add supports | | PUT | `/documents/:id/beam-analyses/:nodeId/supports/:supportId` | Update a support | | DELETE | `/documents/:id/beam-analyses/:nodeId/supports/:supportId` | Delete a support | | POST | `/documents/:id/beam-analyses/:nodeId/loads` | Add loads | | PUT | `/documents/:id/beam-analyses/:nodeId/loads/:loadId` | Update a load | | DELETE | `/documents/:id/beam-analyses/:nodeId/loads/:loadId` | Delete a load | | POST | `/documents/:id/beam-analyses/:nodeId/analyze` | Run analysis | ### Screenshots (screenshot) | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/documents/:id/screenshots` | List screenshot nodes | | GET | `/documents/:id/screenshots/:nodeId` | Get screenshot metadata | | GET | `/documents/:id/screenshots/:nodeId/summary` | Get extended summary | | PUT | `/documents/:id/screenshots/:nodeId` | Update attrs (caption, alignment, etc.) | | GET | `/documents/:id/screenshots/:nodeId/annotations` | Get annotation data | | PUT | `/documents/:id/screenshots/:nodeId/annotations` | Replace annotations | | DELETE | `/documents/:id/screenshots/:nodeId/annotations` | Clear annotations | | PUT | `/documents/:id/screenshots/:nodeId/calibration` | Set calibration | | DELETE | `/documents/:id/screenshots/:nodeId/calibration` | Clear calibration | --- ## Math Syntax Calculations use LaTeX with a placeholder system. There are exactly 4 placeholder types: - `\placeholder[var]{NAME}` — variable name (e.g. `L`, `M_{max}`, `f_{ck}`) - `\placeholder[value]{NUMBER UNIT}` — numeric value with unit - `\placeholder[expr]{EXPRESSION}` — formula referencing other variables - `\placeholder[result]{}` — computed answer (leave empty, system fills it) ### Defining a constant ``` \placeholder[var]{L}\coloneq\placeholder[value]{6\operatorname{\textcolor{blue}{m}}} ``` This defines `L := 6 m`. ### Defining a computed variable ``` \placeholder[var]{M_{max}}\coloneq\placeholder[expr]{\frac{q\cdot L^{2}}{8}}=\placeholder[result]{} ``` This defines `M_max := q·L²/8 = ?` — the result is auto-computed. **IMPORTANT:** The `=\placeholder[result]{}` at the end triggers evaluation. Without it, nothing computes. Always leave the result placeholder empty. ### Assignment operator Always use `\coloneq` (renders as `:=`). Never use `:=` directly. ## Units Units go inline inside `value` or `result` placeholders: ``` \operatorname{\textcolor{blue}{kN}} ``` Common units: `mm`, `cm`, `m`, `km`, `N`, `kN`, `MN`, `Pa`, `kPa`, `MPa`, `GPa`, `kg`, `ton`, `deg`, `rad` Compound: `\frac{\operatorname{\textcolor{blue}{kN}}}{\operatorname{\textcolor{blue}{m}}}` for kN/m ## Unit Conversion Set `unitMemory` on a region to convert display unit or change precision: ```json { "unitMemory": { "preferredUnit": "mm", "precision": 0, "numberFormat": "decimal" } } ``` After updating unitMemory, re-evaluate with `writeBack: true` to apply. ## LaTeX Operations | Operation | LaTeX | |-----------|-------| | Multiply | `\cdot` | | Fraction | `\frac{a}{b}` | | Power | `^{2}` | | Square root | `\sqrt{x}` | | Parentheses | `\left( \right)` | | Subscript | `_{max}` | | Greek | `\alpha`, `\sigma`, `\delta`, `\pi` | ## Region Types Each canvas contains regions of three types: - `"math"` — evaluatable math expressions - `"text"` — HTML text labels (e.g. `<b>Input Parameters</b>`) - `"latex"` — display-only LaTeX (not evaluated) ## Positioning Regions sit on a 2D canvas with `position: {left, top}` in pixels. Evaluation order is top-to-bottom, then left-to-right. Variables must be defined above the expressions that use them. Use `autoLayout: true` in `canvasData` to let the app stack regions vertically. ## Example: Create a Beam Calculation ```bash curl -X POST "$API/documents/$DOC/math-canvases/$CANVAS/regions" \ -H "Authorization: Bearer $ALTODOCS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "regions": [ { "id": "title-1", "position": {"left": 0, "top": 0}, "content": "<b>Simply Supported Beam</b>", "type": "text", "boundingBox": {"width": 300, "height": 36} }, { "id": "def-L", "position": {"left": 0, "top": 0}, "content": "\\placeholder[var]{L}\\coloneq\\placeholder[value]{6\\operatorname{\\textcolor{blue}{m}}}", "type": "math" }, { "id": "def-q", "position": {"left": 0, "top": 0}, "content": "\\placeholder[var]{q}\\coloneq\\placeholder[value]{10\\frac{\\operatorname{\\textcolor{blue}{kN}}}{\\operatorname{\\textcolor{blue}{m}}}}", "type": "math" }, { "id": "calc-M", "position": {"left": 0, "top": 0}, "content": "\\placeholder[var]{M_{max}}\\coloneq\\placeholder[expr]{\\frac{q\\cdot L^{2}}{8}}=\\placeholder[result]{}", "type": "math" } ] }' ``` This creates: L := 6 m, q := 10 kN/m, M_max := q·L²/8 = 45.00 kN·m --- ## Beam Analysis Beam analysis nodes model simply-supported or continuous beams with supports, loads, and material properties. ### Beam Config ```json { "beam_length": 6000, "beam_params": { "E": 210000, "I": 8360000 }, "units": { "length": "mm", "force": "kN", "distributed": "kN/m", "moment": "kN.m", "E": "MPa", "I": "mm4", "deflection": "mm" }, "supports": [ { "location": 0, "type": "pin" }, { "location": 6000, "type": "roller" } ], "loads": [ { "type": "UDLV", "magnitude": -10, "start_location": 0, "end_location": 6000 } ] } ``` ### Support types `"pin"`, `"roller"`, `"fixed"`, `"moment_roller"`, `"y_spring"` (spring requires `ky` stiffness) ### Load types - `"point"` — point load (`location`, `magnitude`) - `"moment"` — applied moment (`location`, `magnitude`) - `"UDLV"` — uniform distributed load (`start_location`, `end_location`, `magnitude`) - `"TrapezoidalLoadV"` — trapezoidal load (`start_location`, `end_location`, `magnitude`, `magnitude_end`) All loads support optional `loadCase` (string) and `factor` (number, default 1.0). ### Run analysis ```bash curl -X POST "$API/documents/$DOC/beam-analyses/$NODE/analyze" \ -H "Authorization: Bearer $ALTODOCS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"writeBack": true}' ``` Returns reactions, shear/moment/deflection data. With `writeBack: true`, results are stored in the document and visible instantly in the browser. ### Example: Configure a simply supported beam ```bash curl -X PUT "$API/documents/$DOC/beam-analyses/$NODE" \ -H "Authorization: Bearer $ALTODOCS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "beam_length": 6000, "beam_params": { "E": 210000, "I": 8360000 }, "supports": [ { "location": 0, "type": "pin" }, { "location": 6000, "type": "roller" } ], "loads": [ { "type": "UDLV", "magnitude": -10, "start_location": 0, "end_location": 6000 } ] }' ``` --- ## Screenshots & Annotations Screenshot nodes are embedded images with an annotation layer (lines, dimensions, rectangles, circles, text boxes, leader labels). ### Reading screenshots ```bash # List all screenshots in a document curl "$API/documents/$DOC/screenshots" \ -H "Authorization: Bearer $ALTODOCS_API_KEY" # Get summary with annotation counts curl "$API/documents/$DOC/screenshots/$NODE/summary" \ -H "Authorization: Bearer $ALTODOCS_API_KEY" ``` The summary includes: `annotationCounts` (per shape type), `totalAnnotations`, `isCalibrated`, `imageStatus` (`"uploaded"` / `"local"` / `"missing"`). ### Updating screenshot metadata ```bash curl -X PUT "$API/documents/$DOC/screenshots/$NODE" \ -H "Authorization: Bearer $ALTODOCS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "caption": "Figure 3 — Foundation plan", "alignment": "center", "showBorder": true, "dimensionUnit": "mm", "dimensionDecimals": 0 }' ``` Updatable fields: `alt`, `title`, `caption`, `alignment` (`"left"` / `"center"` / `"right"`), `captionAlignment`, `width`, `height`, `showBorder`, `defaultStrokeStyle` (`"solid"` / `"dashed"` / `"dotted"`), `dimensionScale`, `dimensionUnit` (`"mm"` / `"cm"` / `"m"`), `dimensionDecimals`, `calibration`. ### Calibration Maps pixel distances to real-world measurements for accurate dimension annotations: ```bash curl -X PUT "$API/documents/$DOC/screenshots/$NODE/calibration" \ -H "Authorization: Bearer $ALTODOCS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"px": 200, "mm": 1000, "scale": 5}' ``` If a known 1-meter wall is 200 pixels: `scale = mm / px = 5`. A 100px dimension then displays as "500 mm". ### Annotations Annotations are geometric shapes drawn on the image. Get them: ```bash curl "$API/documents/$DOC/screenshots/$NODE/annotations" \ -H "Authorization: Bearer $ALTODOCS_API_KEY" ``` Replace all annotations: ```bash curl -X PUT "$API/documents/$DOC/screenshots/$NODE/annotations" \ -H "Authorization: Bearer $ALTODOCS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "lines": [ {"id": "l1", "startX": 100, "startY": 200, "endX": 400, "endY": 200, "color": "#ff0000", "width": 2} ], "dimensions": [ {"id": "d1", "startX": 50, "startY": 400, "endX": 450, "endY": 400, "color": "#0000ff", "width": 2, "text": "", "fontSize": 14, "auto": true} ], "rectangles": [], "circles": [], "textBoxes": [ {"id": "t1", "x": 100, "y": 50, "w": 200, "text": "Column C1", "fontSize": 16, "color": "#000000"} ], "leaders": [] }' ``` #### Annotation shape types - **Line**: `{id, startX, startY, endX, endY, color, width, isArrow?, strokeStyle?}` - **Dimension**: `{id, startX, startY, endX, endY, color, width, text, fontSize, auto?, strokeStyle?}` — with `auto: true`, text is computed from calibration - **Rectangle**: `{id, startX, startY, endX, endY, color, width, strokeStyle?, fillStyle?, fillColor?, fillOpacity?}` - **Circle**: `{id, centerX, centerY, radius, color, width, strokeStyle?, fillStyle?, fillColor?, fillOpacity?}` - **Text Box**: `{id, x, y, w, h?, text, fontSize, color}` - **Leader**: `{id, tipX, tipY, kneeX, kneeY, textX, textY, text, fontSize, color, width, strokeStyle?}` Stroke styles: `"solid"`, `"dashed"`, `"dotted"`. Fill styles: `"none"`, `"solid"`, `"hatch"`, `"crosshatch"`, `"dots"`, `"grid"`. --- ## Tips - Always use `autoLayout: true` for new canvases — the app will stack regions neatly - Use `GET /documents/:id/math-canvases/:nodeId/summary` to quickly see all defined variables and dependencies - After adding regions, the server auto-evaluates and returns results — check the `evaluation` field in the response - Results appear instantly in any open browser via live sync - Only `var`, `value`, `expr`, `result` are valid placeholder types — anything else shows red error squares - For beam analyses, use `/summary` to get support/load type breakdowns before modifying - Screenshot annotations reference pixel coordinates on the original image — use calibration to map to real-world units
