Developer platform
Diagram DSL
The content model the API, CLI, and MCP all speak. Describe a diagram as a node and edge graph, and let Excaliwow lay it out for you.
There are two shapes. A DiagramSpec is a node and edge graph that auto-lays-out, used when you create a diagram. An EditFragment is a small additive patch, used when you change an existing one.
- Create with a DiagramSpec: the API
POST /diagramswith aspecfield, the CLI--specflag, or the MCPgenerate_diagramtool. - Edit with an EditFragment: the API
POST /:id/edit, the CLI--fragmentflag, or the MCPedit_diagramtool.
See the REST API, the CLI, and the MCP server for how each surface accepts these payloads.
DiagramSpec
A DiagramSpec is a list of nodes, an optional list of edges, and an optional layout. Excaliwow positions everything for you.
{
"version": "1",
"nodes": [ /* required, at least 1 */ ],
"edges": [ /* optional */ ],
"layout": { "direction": "TB" }
}versionis optional. Only"1"is accepted. Anything else returnsUNSUPPORTED_VERSION.nodesis required and must contain at least one node.edgesis optional. It accepts the object form or the shorthand string form described below.layoutis optional and tunes direction and spacing.
Nodes
Each node needs a unique id. Everything else is optional.
{
"id": "client",
"label": "Client",
"shape": "rectangle",
"width": 140,
"height": 60,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid"
}| Field | Type | Description |
|---|---|---|
id* | string | Unique within the diagram. Avoid ids ending in -label or containing __, which collide with generated ids and raise ID_COLLISION. |
label | string? | Defaults to id. An empty string suppresses the label entirely. |
shape | enum? | rectangle, ellipse, diamond, or text. Defaults to rectangle. |
width / height | number? | Positive. Treated as a minimum, so a shape grows to fit its label. |
strokeColor | string? | Any CSS color. |
backgroundColor | string? | Any CSS color, or transparent. |
fillStyle | enum? | hachure, cross-hatch, solid, or zigzag. |
When you omit width and height, default sizes are a rectangle at 140x60, an ellipse at 140x80, and a diamond at 160x90. A text node is sized to its text.
Edges
An edge connects two nodes by id. There are two ways to write one.
Object form
{ "from": "client", "to": "api", "label": "request", "arrowhead": "arrow" }| Field | Type | Description |
|---|---|---|
from / to* | string | Each must name an existing node id, otherwise the edge is rejected with DANGLING_EDGE. |
label | string? | Optional text rendered on the edge. |
arrowhead | enum? | arrow, triangle, or null for no head. Defaults to arrow. |
Shorthand string
DiagramSpec edges also accept a compact string. Everything after the first colon becomes the label. This form is not available in an EditFragment.
"client -> api"
"client -> api: request"A missing or empty endpoint, or anything other than a single ->, returns MALFORMED_SHORTHAND.
Layout
Layout sets the flow direction and the spacing between nodes.
{ "direction": "TB", "nodeSep": 50, "rankSep": 80 }directionis one ofTB(top to bottom),BT,LR, orRL. Defaults toTB.nodeSep(default 50) andrankSep(default 80) are optional positive numbers controlling node spacing.
EditFragment
An edit additively merges a fragment into an existing diagram. Existing elements are never moved, resized, or deleted, so an edit can only add new content or restyle nodes you already have.
{
"addNodes": [ /* NodeSpec, plus optional absolute x and y */ ],
"addEdges": [ /* Edge, object form only, no shorthand */ ],
"updateNodes": [ /* { "id": "..." } plus restyle fields only */ ]
}addNodestakes the same fields as a NodeSpec. New ids must not collide with any existing element id, otherwise the edit returnsID_COLLISION. You may set an explicitxandy(both or neither). Otherwise the node is auto-placed below the existing diagram.addEdgesaccepts the object form only.fromandtoresolve against existing nodes plus any you add in the same fragment.updateNodespatches existing nodes by id. Only the restyle fields can change:strokeColor,backgroundColor,fillStyle, andlabel.
Geometry fields are rejected on update
x, y, width, height, or shape, is rejected with INVALID_SPEC in updateNodes. This keeps an already-placed node from drifting into a neighbor.Worked example
A small but realistic DiagramSpec: a client calling an API server that reads from a database and a cache.
{
"version": "1",
"nodes": [
{ "id": "client", "label": "Client", "shape": "rectangle" },
{ "id": "api", "label": "API Server", "shape": "rectangle" },
{ "id": "db", "label": "Database", "shape": "diamond" },
{ "id": "cache", "label": "Cache", "shape": "ellipse" }
],
"edges": [
{ "from": "client", "to": "api", "label": "request" },
{ "from": "api", "to": "db" },
{ "from": "api", "to": "cache" }
],
"layout": { "direction": "TB" }
}Caps
A spec that exceeds a cap is rejected before it renders.
| Limit | Maximum | Description |
|---|---|---|
nodes | 1000 | Nodes per diagram. |
edges | 2000 | Edges per diagram. |
label length | 2000 | Characters per label. |
total scene elements | 5000 | Elements in the rendered scene (nodes, edges, and labels combined). |
Error codes
When a spec or fragment is invalid, the response carries one of these codes, surfaced as code: message.
INVALID_SPECUNSUPPORTED_VERSIONMISSING_NODE_IDDUPLICATE_NODE_IDUNKNOWN_SHAPEDANGLING_EDGEMALFORMED_SHORTHANDMALFORMED_EDGEID_COLLISIONSPEC_TOO_LARGESCENE_TOO_LARGEPROTO_KEY
For how these codes arrive in an HTTP response, see the REST API reference.