Skip to content

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

  1. Open Settings › MCP / REST server in Eidograph.
  2. Enable the server and the tools your client will call.
  3. In Figures, make the target figure's exposure dot filled.
  4. Confirm that the status is running. The default base URL is:
txt
http://127.0.0.1:14159/api/v1

Discover the enabled tools, then append a construction to the current figure:

bash
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:

json
{
  "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:

http
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:

http
X-Eidograph-Client: my-automation

Eidograph 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_figure creates and activates a figure. select_object changes 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:

http
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:

json
{
  "arguments": {
    "name": "Figure 2"
  }
}

No-argument tools may receive {} or an empty request body.

Response and error model

Tool success uses this envelope:

json
{
  "ok": true,
  "result": "tool-specific value"
}

Tool failure uses:

json
{
  "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 statusMeaning
200 OKStatus/activity read, successful discovery, or successful tool call
400 Bad RequestTool error, invalid arguments, disabled/unknown tool, unavailable figure, compiler rejection, or exhausted allowance
403 ForbiddenUntrusted Origin on discovery or a tool call
503 Service UnavailableTool discovery could not reach the Eidograph UI bridge
404 Not FoundUnknown route
405 Method Not AllowedKnown 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.

bash
curl http://127.0.0.1:14159/api/v1/status

Example while running in loopback mode:

json
{
  "running": true,
  "bridgeReady": true,
  "bindAddress": "127.0.0.1",
  "port": 14159,
  "lanAddresses": [],
  "lastError": null
}
FieldTypeDescription
runningbooleanThe HTTP listener is running.
bridgeReadybooleanThe app UI can currently execute tool requests.
bindAddressstring or nullListener address, normally 127.0.0.1 or 0.0.0.0.
portinteger or nullActual listener port.
lanAddressesarray of stringsEvery base address a same-network client can dial, best first (private ranges first), at most 10. Empty unless LAN mode is enabled.
lastErrorstring or nullMost 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.

bash
curl http://127.0.0.1:14159/api/v1/activity
json
{
  "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.

bash
curl \
  -H "X-Eidograph-Client: schema-reader" \
  http://127.0.0.1:14159/api/v1/tools

Abbreviated response:

json
{
  "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.

bash
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.

bash
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:

FieldTypeDescription
namestringFigure display name.
kindplane or solidFixed 2D/3D dialect.
activebooleanWhether current-figure tools target it.

Decoded result example:

json
[
  { "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:

json
{ "name": "Solid model" }
ArgumentTypeRequiredDescription
namestringyesName 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:

json
{
  "kind": "plane",
  "name": "API construction"
}
ArgumentTypeRequiredDescription
kindplane or solidyesThe figure's fixed Eidolang dialect.
namestringnoRequested 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:

json
{
  "commands": "point O 0 0\ncircle c O radius 3"
}
ArgumentTypeRequiredDescription
commandsstringyesOne or more newline-separated Eidolang commands.

Result: a diagnostics summary. The edit is committed only when compilation has no errors or skipped-command warnings.

json
{
  "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:

json
{
  "source": "space plane\npoint O 0 0\ncircle c O radius 3\n"
}
ArgumentTypeRequiredDescription
sourcestringyesFull 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:

FieldTypeDescription
namestringEidolang object identifier.
kindstringCommand/object kind.
tierstring or nullFreedom tier such as free, bound, or derived.
statusstringEvaluation status.
infostringShort value summary, or the failure reason for an invalid object.

Decoded result example:

json
[
  { "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.

json
[
  { "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:

json
{ "name": "s" }
ArgumentTypeRequiredDescription
namestringyesAn 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:

json
{
  "figure": "Construction",
  "format": "png",
  "region": "content",
  "scale": 2,
  "transparent": true,
  "grid": false
}
ArgumentTypeRequiredDescription
figurestringnoExact exposed figure name; defaults to the active figure.
formatsvg, png, gif, or mp4yesOutput encoding.
regionview or contentnoCurrent view or fitted figure; defaults to content.
cropobjectnoPlane-only screen-space rectangle { x, y, width, height }; overrides region. Width and height must be positive.
scale1, 2, or 4noRaster pixel-density multiplier for png, gif, and mp4; defaults to the app export preference.
transparentbooleannoPlane still images only; defaults to the app export preference.
gridbooleannoInclude coordinate grid; defaults to the app export preference.
fpsinteger 1–60noAnimation frame rate for gif and mp4; defaults to the app export preference.
secondsnumber 0.1–3600noAnimation duration for gif and mp4; defaults to one loop of the timeline.
modetimeline or orbitnoMotion 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.

json
{
  "format": "mp4",
  "mode": "orbit",
  "seconds": 6,
  "fps": 30,
  "scale": 2
}

Result: object:

json
{
  "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:

json
{
  "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:

json
{ "path": "C:\\Users\\me\\Documents\\Construction.eido" }
ArgumentTypeRequiredDescription
pathstringconditionallyAbsolute 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:

json
{ "path": "C:\\Users\\me\\Documents\\Construction.eido" }
ArgumentTypeRequiredDescription
pathstringyesAbsolute 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.

js
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.

python
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+

powershell
$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.result

Create a figure and replace its script

bash
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

SymptomWhat to check
Connection refusedEnable the server, confirm the port, keep Eidograph open, and check /status.
bridgeReady is falseWait for the project UI to finish loading or restart the app.
tool ... is disabledEnable it under Settings › MCP / REST server › Tools, then rediscover tools.
no figure is openCall create_figure, or open and expose a figure in the app.
the current figure is not exposedFill its exposure dot in Figures.
“edit not applied” in a successful resultFix the first compiler diagnostic and retry; the existing script is intact.
403 untrusted OriginUse a native/CLI client or remove the non-local Origin; do not weaken LAN security.
Export cannot switch figuresFinish the operation that locks figure switching, activate the target manually, or export the current figure.
Daily limit errorReview Settings › Account and wait for the daily reset or unlock Pro.
Request times out after 30 secondsBring 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.