REST API Reference
Eidograph's REST API lets a local script or custom agent inspect and edit the project open in the Windows app. It exposes the same tools, compiler validation, undo history, figure permissions, and usage limits as the MCP server.
The API is served by the installed Windows app. Eidograph must remain open while a client uses it; there is no hosted REST service or user-facing Web edition.
Quick start
- Open Settings › MCP / REST server in Eidograph.
- Enable the server and the tools your client will call.
- In Figures, make the target figure's exposure dot filled.
- Confirm that the status is running. The default base URL is:
http://127.0.0.1:14159/api/v1Discover the enabled tools, then append a construction to the current figure:
curl http://127.0.0.1:14159/api/v1/tools
curl -X POST http://127.0.0.1:14159/api/v1/tools/append_commands \
-H "Content-Type: application/json" \
-H "X-Eidograph-Client: geometry-script" \
-d '{"commands":"point A -2 0\npoint B 2 0\nsegment s A B"}'A successful tool call returns:
{
"ok": true,
"result": "ok — no diagnostics"
}Transport and security
Base URL and JSON
All paths below are relative to http://127.0.0.1:14159/api/v1. If the port changes in Settings, update the URL — or read the current one from the discovery file the app publishes while its server runs, described in MCP, REST, and the in-app Agent. Tool calls use POST with a JSON object and Content-Type: application/json. Responses are JSON except for framework-level errors such as an unsupported method.
There is no API version negotiation: /api/v1 is the current REST surface.
Authentication and network scope
The server has no TLS, and no password unless you give it one. Loopback mode is the safe default and accepts clients on the same computer only. Allow clients on the same network binds the server to 0.0.0.0; any device that can reach the port can invoke every enabled tool against every exposed figure.
Turn on Require an access token in the same settings pane — always before enabling LAN mode — and every request must carry the generated secret:
Authorization: Bearer <token>Requests without it are answered 401 with {"ok":false,"error":"missing or invalid bearer token"}. This covers /status and /activity as well as tool calls. The token is published in the discovery file, so a client on this machine can read it rather than being configured with it.
DANGER
Use LAN mode only on a trusted network. Do not forward the port, publish it through a tunnel, or expose it to the internet.
The optional request header below is a display label, not authentication:
X-Eidograph-Client: my-automationEidograph uses it as the caller name in external activity. Without it, the caller's IP address is recorded.
Origin checks
Requests without an Origin header—normal for command-line tools, Node.js, Python, PowerShell, and native agents—are accepted. If a client sends Origin, its host must be localhost, 127.0.0.1, or ::1; otherwise tool discovery and tool calls return 403.
The REST API is intended for native and command-line clients, not arbitrary browser pages. Browser CORS and preflight behavior may block a request even when its origin host is local.
Access and side effects
Every tool call is checked at execution time:
- Settings › MCP / REST server › Tools controls the tool allow-list.
- A figure's filled exposure dot controls whether external clients can see it.
- Current-figure tools act on the project and figure active in the UI.
create_figurecreates and activates a figure.select_objectchanges the canvas selection.- Script edits compile before commit. An error or skipped-command warning rejects the whole edit, leaving the current script unchanged.
- Successful edits redraw immediately, persist in the workspace, and enter normal undo history.
Do not send concurrent edits that depend on the same script version. Serialize them and call get_script again before a rewrite.
Common headers and request bodies
The direct body form is recommended:
POST /api/v1/tools/get_figure_script
Content-Type: application/json
X-Eidograph-Client: docs-example
{"name":"Figure 2"}For clients that already use an RPC-style envelope, the server also accepts an arguments wrapper:
{
"arguments": {
"name": "Figure 2"
}
}No-argument tools may receive {} or an empty request body.
Response and error model
Tool success uses this envelope:
{
"ok": true,
"result": "tool-specific value"
}Tool failure uses:
{
"ok": false,
"error": "tool `set_script` is disabled"
}Most read tools return text. list_figures, list_objects, and list_commands return a JSON-encoded string inside result, so parse result once more when structured data is needed. export_figure is the exception: it returns a JSON object directly.
| HTTP status | Meaning |
|---|---|
200 OK | Status/activity read, successful discovery, or successful tool call |
400 Bad Request | Tool error, invalid arguments, disabled/unknown tool, unavailable figure, compiler rejection, or exhausted allowance |
403 Forbidden | Untrusted Origin on discovery or a tool call |
503 Service Unavailable | Tool discovery could not reach the Eidograph UI bridge |
404 Not Found | Unknown route |
405 Method Not Allowed | Known route called with an unsupported HTTP method |
A malformed JSON document or wrong content type can produce a framework error rather than the normal { "ok": false } envelope. Clients should check both the HTTP status and the JSON ok field when present.
Endpoints
GET /status
Returns native server state directly, without an ok envelope.
curl http://127.0.0.1:14159/api/v1/statusExample while running in loopback mode:
{
"running": true,
"bridgeReady": true,
"bindAddress": "127.0.0.1",
"port": 14159,
"lanAddresses": [],
"lastError": null
}| Field | Type | Description |
|---|---|---|
running | boolean | The HTTP listener is running. |
bridgeReady | boolean | The app UI can currently execute tool requests. |
bindAddress | string or null | Listener address, normally 127.0.0.1 or 0.0.0.0. |
port | integer or null | Actual listener port. |
lanAddresses | array of strings | Every base address a same-network client can dial, best first (private ranges first), at most 10. Empty unless LAN mode is enabled. |
lastError | string or null | Most recent listener error. |
running: true and bridgeReady: false means the port is open but tools cannot execute yet. Wait for both to be true.
GET /activity
Returns up to 200 most recent MCP and REST tool calls, newest first. Status checks, activity reads, and tool discovery are not recorded.
curl http://127.0.0.1:14159/api/v1/activity{
"activity": [
{
"id": "342daa36-e4d3-4c96-aebe-d358d60a96dd",
"at": 1788112800123,
"transport": "rest",
"caller": "geometry-script",
"tool": "append_commands",
"ok": true,
"durationMs": 18
}
]
}at is Unix time in milliseconds. durationMs measures the complete bridge/tool call. The caller label is supplied by the client and should not be treated as a verified identity.
GET /tools
Discovers only the tools currently enabled in Settings. It does not consume a tool-call allowance.
curl \
-H "X-Eidograph-Client: schema-reader" \
http://127.0.0.1:14159/api/v1/toolsAbbreviated response:
{
"ok": true,
"tools": [
{
"name": "append_commands",
"description": "Append one or more command lines...",
"inputSchema": {
"type": "object",
"properties": {
"commands": {
"type": "string",
"description": "command lines, newline-separated"
}
},
"required": ["commands"]
}
}
]
}Treat this endpoint as the runtime authority: tool descriptions and schemas come from the same definitions used by the app and MCP server.
POST /tools/{tool}
Executes one enabled tool. Replace {tool} with a discovered tool name and put its arguments in the JSON body.
curl -X POST http://127.0.0.1:14159/api/v1/tools/list_objects \
-H "Content-Type: application/json" \
-d '{}'Every executed call consumes an external API allowance, including a call that reaches a tool and then fails. Status, activity, and discovery requests are free. An export also uses the export allowance after it successfully produces bytes. Eidograph Pro removes these limits; the app's Settings › Account page shows the current free allowance.
Tool reference
The schemas returned by GET /tools are authoritative. The catalog below documents the full current surface; disabled tools are omitted from discovery and rejected if called directly.
get_script
Reads the active, exposed figure's complete Eidolang source.
Body: {}
Result: string containing the source exactly as stored.
curl -sS -X POST "$EIDO_API/tools/get_script" \
-H "Content-Type: application/json" -d '{}'list_figures
Lists exposed figures in the active project, ordered by creation time. Unexposed figures are omitted.
Body: {}
Result: JSON-encoded array string with:
| Field | Type | Description |
|---|---|---|
name | string | Figure display name. |
kind | plane or solid | Fixed 2D/3D dialect. |
active | boolean | Whether current-figure tools target it. |
Decoded result example:
[
{ "name": "Construction", "kind": "plane", "active": true },
{ "name": "Solid model", "kind": "solid", "active": false }
]get_figure_script
Reads an exposed figure by exact display name without activating it. Edits still target the active figure.
Body:
{ "name": "Solid model" }| Argument | Type | Required | Description |
|---|---|---|---|
name | string | yes | Name returned by list_figures. |
Result: source string.
create_figure
Creates a figure in the active project and makes it active. Use it when the project is empty or the work belongs in a new figure.
Body:
{
"kind": "plane",
"name": "API construction"
}| Argument | Type | Required | Description |
|---|---|---|---|
kind | plane or solid | yes | The figure's fixed Eidolang dialect. |
name | string | no | Requested display name; Eidograph supplies one when omitted. |
Result: confirmation string.
append_commands
Appends newline-separated Eidolang commands to the active figure. Prefer it for additive edits because it preserves all existing lines.
Body:
{
"commands": "point O 0 0\ncircle c O radius 3"
}| Argument | Type | Required | Description |
|---|---|---|---|
commands | string | yes | One or more newline-separated Eidolang commands. |
Result: a diagnostics summary. The edit is committed only when compilation has no errors or skipped-command warnings.
{
"ok": true,
"result": "error line 2: unknown command `circel`\n hint: ...\nedit not applied — the current script is unchanged. Fix the first reported issue; later errors may be consequences, then retry."
}Compiler rejection is reported in result because the tool executed successfully but declined to commit the candidate. Check the returned text rather than relying only on ok.
set_script
Replaces the active figure's complete source. Use it only when existing lines must be changed or removed. Read get_script immediately beforehand to avoid overwriting a newer edit.
Body:
{
"source": "space plane\npoint O 0 0\ncircle c O radius 3\n"
}| Argument | Type | Required | Description |
|---|---|---|---|
source | string | yes | Full replacement source. |
Result: diagnostics summary. Like append_commands, the replacement is transactional and may return ok: true with an “edit not applied” diagnostic.
list_objects
Lists the compiled objects in the active figure.
Body: {}
Result: JSON-encoded array string with:
| Field | Type | Description |
|---|---|---|
name | string | Eidolang object identifier. |
kind | string | Command/object kind. |
tier | string or null | Freedom tier such as free, bound, or derived. |
status | string | Evaluation status. |
info | string | Short value summary, or the failure reason for an invalid object. |
Decoded result example:
[
{ "name": "A", "kind": "point", "tier": "free", "status": "ok", "info": "(-2, 0)" },
{ "name": "s", "kind": "segment", "tier": "derived", "status": "ok", "info": "len 4" }
]list_diagnostics
Returns current compiler and runtime diagnostics for the active figure.
Body: {}
Result: human-readable string. With no diagnostics it is ok — no diagnostics; otherwise each item includes severity, optional line, message, and optional hint.
list_commands
Lists the live command registry for the active figure's plane or solid dialect. Use it before generating unfamiliar Eidolang.
Body: {}
Result: JSON-encoded array string. Each decoded item contains kind and signature.
[
{ "kind": "point", "signature": "point <name> <x> <y> | point <name> polar <r> <theta> | ..." },
{ "kind": "segment", "signature": "segment <name> <pointA> <pointB>" }
]Signatures can evolve with the app, so generated clients should prefer discovery over a hard-coded command list.
select_object
Selects and highlights a compiled object in the active canvas so the user can see what the client means.
Body:
{ "name": "s" }| Argument | Type | Required | Description |
|---|---|---|---|
name | string | yes | An object name returned by list_objects. |
Result: confirmation string such as selected s.
export_figure
Exports an exposed figure through its live renderer. A named, non-active figure is mounted temporarily and the original active figure is restored afterward.
Body:
{
"figure": "Construction",
"format": "png",
"region": "content",
"scale": 2,
"transparent": true,
"grid": false
}| Argument | Type | Required | Description |
|---|---|---|---|
figure | string | no | Exact exposed figure name; defaults to the active figure. |
format | svg, png, gif, or mp4 | yes | Output encoding. |
region | view or content | no | Current view or fitted figure; defaults to content. |
crop | object | no | Plane-only screen-space rectangle { x, y, width, height }; overrides region. Width and height must be positive. |
scale | 1, 2, or 4 | no | Raster pixel-density multiplier for png, gif, and mp4; defaults to the app export preference. |
transparent | boolean | no | Plane still images only; defaults to the app export preference. |
grid | boolean | no | Include coordinate grid; defaults to the app export preference. |
fps | integer 1–60 | no | Animation frame rate for gif and mp4; defaults to the app export preference. |
seconds | number 0.1–3600 | no | Animation duration for gif and mp4; defaults to one loop of the timeline. |
mode | timeline or orbit | no | Motion source for gif and mp4; defaults to timeline. |
Solid figures reject transparent: true, and custom crops are supported only for plane figures.
Animated exports
gif and mp4 render the figure's animate and stage timeline frame by frame. mode: "orbit" instead spins the camera around a solid figure, which works on a static model but is rejected for plane figures. Transparency is not available for either format. A render is capped at 1800 frames, so a long, high-frame-rate request is trimmed.
These calls take much longer than a still export — seconds to minutes — and export_figure is given a 600-second budget rather than the 30 seconds every other tool has. MP4 also needs H.264 WebCodecs support in the app's webview.
{
"format": "mp4",
"mode": "orbit",
"seconds": 6,
"fps": 30,
"scale": 2
}Result: object:
{
"figure": "Construction",
"fileName": "Construction.png",
"format": "png",
"mimeType": "image/png",
"encoding": "base64",
"data": "iVBORw0KGgoAAA..."
}SVG returns mimeType: "image/svg+xml", encoding: "utf8", and SVG markup in data. PNG, GIF, and MP4 return base64 data with no data: URL prefix, under image/png, image/gif, and video/mp4 respectively.
Animated results carry an extra animation object describing what was actually rendered, so you can tell when the frame cap shortened the request:
{
"animation": { "mode": "orbit", "seconds": 6, "frames": 180, "fps": 30, "capped": false }
}Over MCP, PNG and GIF arrive as native image content and MP4 as an embedded binary resource, because MCP has no video content type.
save_project
Saves the active project to disk as a .eido package. Writes directly to a path — no native save dialog appears, since none of these tools have a user present to click one.
Body:
{ "path": "C:\\Users\\me\\Documents\\Construction.eido" }| Argument | Type | Required | Description |
|---|---|---|---|
path | string | conditionally | Absolute file path to save to. Required the first time a project is saved; omit afterward to overwrite the project's known file. .eido is appended if missing. |
Result: the absolute path the project was saved to.
open_project
Opens a .eido package (or a bare .geo/.txt script) from an absolute file path as a new project tab — the headless equivalent of the app's Open dialog.
Body:
{ "path": "C:\\Users\\me\\Documents\\Construction.eido" }| Argument | Type | Required | Description |
|---|---|---|---|
path | string | yes | Absolute file path to open. |
Result: confirmation string once the project is open and active.
Complete client examples
JavaScript (Node.js 20+)
This is server-side Node.js code, not browser code.
const baseUrl = 'http://127.0.0.1:14159/api/v1'
async function callTool(name, arguments_ = {}) {
const response = await fetch(`${baseUrl}/tools/${encodeURIComponent(name)}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Eidograph-Client': 'node-example',
},
body: JSON.stringify(arguments_),
})
const body = await response.json()
if (!response.ok || !body.ok) {
throw new Error(body.error || `HTTP ${response.status}`)
}
return body.result
}
const figures = JSON.parse(await callTool('list_figures'))
console.log(figures)
const diagnostics = await callTool('append_commands', {
commands: 'point A -2 0\npoint B 2 0\nsegment s A B',
})
console.log(diagnostics)Python 3, including PNG export
This example uses only the Python standard library.
import base64
import json
from pathlib import Path
from urllib.error import HTTPError
from urllib.request import Request, urlopen
BASE_URL = "http://127.0.0.1:14159/api/v1"
def call_tool(name, arguments=None):
payload = json.dumps(arguments or {}).encode("utf-8")
request = Request(
f"{BASE_URL}/tools/{name}",
data=payload,
method="POST",
headers={
"Content-Type": "application/json",
"X-Eidograph-Client": "python-example",
},
)
try:
with urlopen(request) as response:
body = json.load(response)
except HTTPError as error:
body = json.load(error)
raise RuntimeError(body.get("error", f"HTTP {error.code}")) from error
if not body.get("ok"):
raise RuntimeError(body.get("error", "unknown Eidograph error"))
return body["result"]
print(call_tool("list_diagnostics"))
export = call_tool("export_figure", {
"format": "png",
"region": "content",
"scale": 2,
"grid": False,
})
Path(export["fileName"]).write_bytes(base64.b64decode(export["data"]))
print(f"saved {export['fileName']}")For SVG, write export["data"] as UTF-8 text instead of base64-decoding it.
PowerShell 7+
$baseUrl = 'http://127.0.0.1:14159/api/v1'
$headers = @{ 'X-Eidograph-Client' = 'powershell-example' }
$commands = @'
point O 0 0
circle c O radius 3
point P on c at 45deg
segment radius O P
'@
$body = @{ commands = $commands } | ConvertTo-Json
$response = Invoke-RestMethod `
-Method Post `
-Uri "$baseUrl/tools/append_commands" `
-Headers $headers `
-ContentType 'application/json' `
-Body $body
if (-not $response.ok) { throw $response.error }
$response.resultCreate a figure and replace its script
export EIDO_API='http://127.0.0.1:14159/api/v1'
curl -sS -X POST "$EIDO_API/tools/create_figure" \
-H 'Content-Type: application/json' \
-H 'X-Eidograph-Client: curl-workflow' \
-d '{"kind":"plane","name":"Circle API demo"}'
curl -sS -X POST "$EIDO_API/tools/set_script" \
-H 'Content-Type: application/json' \
-H 'X-Eidograph-Client: curl-workflow' \
-d '{"source":"space plane\npoint O 0 0\ncircle c O radius 3\npoint P on c at 45deg\nsegment radius O P\n"}'
curl -sS -X POST "$EIDO_API/tools/list_diagnostics" \
-H 'Content-Type: application/json' \
-d '{}'Troubleshooting
| Symptom | What to check |
|---|---|
| Connection refused | Enable the server, confirm the port, keep Eidograph open, and check /status. |
bridgeReady is false | Wait for the project UI to finish loading or restart the app. |
tool ... is disabled | Enable it under Settings › MCP / REST server › Tools, then rediscover tools. |
no figure is open | Call create_figure, or open and expose a figure in the app. |
the current figure is not exposed | Fill its exposure dot in Figures. |
| “edit not applied” in a successful result | Fix the first compiler diagnostic and retry; the existing script is intact. |
403 untrusted Origin | Use a native/CLI client or remove the non-local Origin; do not weaken LAN security. |
| Export cannot switch figures | Finish the operation that locks figure switching, activate the target manually, or export the current figure. |
| Daily limit error | Review Settings › Account and wait for the daily reset or unlock Pro. |
| Request times out after 30 seconds | Bring Eidograph to a healthy state, avoid parallel long-running calls, and retry after checking /status. |
For agent-oriented discovery and resources over Streamable HTTP, see MCP, REST, and the in-app Agent.
