eDraw documentation
eDraw is a browser-based editor for system diagrams — the boxes-and-arrows drawings that describe how the parts of a system fit together. This page covers both halves of it: the editor a person uses, and the API an AI agent uses to create, update, document and manage the same drawings.
Overview
A drawing in eDraw is a graph. Components (servers, databases, queues, users, and so on) are the nodes; connections between their ports are the edges. Everything sits on an artboard — a fixed-size page such as A4 or 1080p — which is what gets exported.
Diagrams belong to a user account, are addressed by UUID, and auto-save while you work. The same diagram can be opened in the editor, driven through the API, and rendered as a written document — all three views read the same record.
/api/aiThe editor
Open /app. The left palette holds components and shapes; drag one onto the canvas or click to place it at the centre.
| Action | How |
|---|---|
| Add a component | Drag from the palette, or click a palette item |
| Connect two components | Drag from a port (the dot on an edge) to another component's port |
| Edit text / colour | Select the element, then use the properties panel on the right |
| Label a connection | Click the connection, then type in the label field |
| Change the page | Artboard menu in the toolbar — A4, Letter, 1080p, 4K, custom |
| Save | Ctrl/Cmd + S; autosave also runs every 30 seconds once a diagram has been saved once |
| Export | Toolbar export menu — PNG, SVG, PDF, JSON, Markdown |
| Undo / redo | Ctrl/Cmd + Z, Ctrl/Cmd + Shift + Z |
The interface is available in English and Bangla; the language toggle sits in the toolbar.
Accounts & sharing
Public registration is disabled — an administrator creates accounts from the admin panel. Each user sees only their own diagrams, organised with per-user categories.
Live share broadcasts the board you are editing over a socket to anyone holding the share link, view-only, for as long as the session lasts. Shares are held in memory and expire 24 hours after the last update — they are for presenting, not for storage.
How the AI layer works
The editor stores a diagram as raw canvas state: pixel coordinates, numeric element ids, port names. That is a poor thing to ask a language model to author. So the API speaks a declarative spec instead — a list of nodes and edges with no coordinates — and the server compiles it into real diagram data, computing the layout itself.
spec (nodes + edges) ──compile──▶ diagram data (coordinates, ids, ports)
spec ◀─decompile── diagram data
patch ops ──apply────▶ diagram data (in place, layout preserved)
diagram data ──describe──▶ Markdown documentation
Three rules follow from that, and they matter more than any endpoint detail:
- Never compute coordinates. Send nodes and edges; the server lays them out.
Pin a node with explicit
x/yonly when you deliberately want it fixed. - Address nodes by
key, not by id. Keys are stable, survive a round-trip through the editor, and are what patches refer to. - Prefer
PATCHoverPUT. A patch edits the existing drawing and keeps manual changes a person made; a PUT replaces the whole thing.
Authentication
Create a key at /api-keys. Send it on every request:
X-API-Key: edk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Authorization: Bearer <key> and ?api_key= also work. A signed-in
browser session authenticates too, which is how the in-app screens call the same endpoints.
| Scope | Allows |
|---|---|
read | List and read diagrams, specs, documentation, activity |
write | Everything above, plus create, update, patch, delete, and write documentation |
Rate limit: 240 requests per minute per key, answered with 429 when exceeded.
Quick start
1. Create a diagram
curl -X POST https://edraw.void.bd/api/ai/diagrams \
-H "X-API-Key: $EDRAW_KEY" -H "Content-Type: application/json" \
-d '{
"title": "Payment flow",
"category": "Architecture",
"spec": {
"layout": { "direction": "LR" },
"nodes": [
{ "key": "user", "type": "user", "label": "Customer" },
{ "key": "api", "type": "api", "label": "API Gateway", "subtitle": "Kong" },
{ "key": "pay", "type": "microservice", "label": "Payment Service",
"details": "Validates the card, calls the PSP, writes the ledger entry." },
{ "key": "db", "type": "database", "label": "PostgreSQL" },
{ "key": "mq", "type": "queue", "label": "RabbitMQ" }
],
"edges": [
{ "from": "user", "to": "api", "label": "HTTPS" },
{ "from": "api", "to": "pay", "label": "gRPC" },
{ "from": "pay", "to": "db", "label": "SQL" },
{ "from": "pay", "to": "mq", "label": "payment.settled" }
]
}
}'
The response carries the new UUID, an editor URL, and the summary of the documentation that was generated alongside it:
{
"success": true,
"uuid": "8f3c…",
"title": "Payment flow",
"stats": { "nodes": 5, "edges": 4, "direction": "LR" },
"warnings": [],
"url": "/app?diagram=8f3c…",
"docUrl": "/api/ai/diagrams/8f3c…/doc"
}
2. Read it back
curl -H "X-API-Key: $EDRAW_KEY" \
https://edraw.void.bd/api/ai/diagrams/8f3c…/spec
3. Change it
curl -X PATCH https://edraw.void.bd/api/ai/diagrams/8f3c… \
-H "X-API-Key: $EDRAW_KEY" -H "Content-Type: application/json" \
-d '{ "ops": [
{ "op": "addNode", "node": { "key": "redis", "type": "cache", "label": "Redis" } },
{ "op": "addEdge", "from": "pay", "to": "redis", "label": "idempotency keys" }
] }'
4. Read its documentation
curl -H "X-API-Key: $EDRAW_KEY" \
"https://edraw.void.bd/api/ai/diagrams/8f3c…/doc?format=md"
The diagram spec
The full JSON Schema is served at /api/ai/schema.
Node
| Field | Type | Meaning |
|---|---|---|
key | string | Stable identifier used by edges and patches. Defaults to a slug of the label. |
type | string | One of the component types. Defaults to server. |
label | string | Main text on the box. |
subtitle | string | Smaller second line — good for the concrete technology ("PostgreSQL 16"). |
details | string | A bullet list — one bullet per line. Drawn inside the box and repeated in the generated documentation. The node widens to fit the longest bullet. |
color, textColor | string | Hex overrides. Defaults come from the type's palette colour. |
layout | string | compact (default) or card for the taller card style. |
width, height | number | Override the default size. |
x, y | number | Pin the node. Auto-layout skips pinned nodes. Both are required together. |
Edge
| Field | Type | Meaning |
|---|---|---|
from, to | string | Required. Node keys. An unknown key is a 400, not a silent skip. |
label | string | Text drawn on the line — the protocol, the payload, the trigger. |
arrow | string | end (default), both, or none. |
fromPort, toPort | string | top / right / bottom / left. Chosen automatically from the flow direction if omitted. |
Top level
| Field | Meaning |
|---|---|
nodes | Required. 1–500 nodes. |
edges | Up to 2000 edges. |
layout | { direction, gapX, gapY } — see Layout. |
artboard | A size name, an object { size, width, height, bgColor }, or false for none. Omit it and the smallest standard page that fits is chosen. |
Artboard sizes: A4, A4-landscape, A5, A5-landscape,
A3, A3-landscape, Letter, Letter-landscape,
Legal, Legal-landscape, 1080p, 4K, custom.
POST /api/ai/preview to compile a spec and see the resulting layout, warnings
and thumbnail without saving anything. It is the cheapest way to check a spec is valid.
Component types
Pick the type that matches what the thing is — it sets the icon, the default colour,
and how the component is grouped in generated documentation. The live list is in
/api/ai/manifest.
note, label, textbox and image are annotations —
they are listed separately in documentation and left out of flow analysis. The eight shape types
(rectangle, circle, ellipse, triangle,
diamond, hexagon, parallelogram, star) are plain
geometry for grouping and decoration.
Layout
The server runs a layered layout. Nodes are ranked by longest path along the edges, so anything downstream lands in a later column; nodes within a rank are then ordered by the average position of their predecessors, which keeps crossings down. Cycles are tolerated — ranks simply stop growing.
| Direction | Flow | Default ports |
|---|---|---|
LR | Left to right (default) | right → left |
RL | Right to left | left → right |
TB | Top to bottom | bottom → top |
BT | Bottom to top | top → bottom |
gapX controls the space between ranks, gapY the space within a rank.
Defaults are 90 and 60.
To re-run the layout on an existing drawing — after adding several nodes, say — send
{ "op": "relayout", "direction": "TB" }. Be aware that this discards any manual
positioning a person did in the editor.
Updating with patches
A patch is a list of operations applied in order. This is the right way to evolve a diagram: it touches only what you name and leaves the rest — including human edits — alone.
PATCH /api/ai/diagrams/{uuid}
{
"expectedUpdatedAt": "2026-08-08 01:22:07",
"ops": [
{ "op": "addNode", "node": { "key": "cdn", "type": "cdn", "label": "Cloudflare" } },
{ "op": "addEdge", "from": "user", "to": "cdn", "label": "HTTPS" },
{ "op": "updateNode", "key": "db", "set": { "subtitle": "PostgreSQL 16", "color": "#0e7490" } },
{ "op": "updateEdge", "from": "api", "to": "pay", "set": { "label": "gRPC / mTLS" } },
{ "op": "removeEdge", "from": "user", "to": "api" },
{ "op": "setTitle", "title": "Payment flow (v2)" },
{ "op": "relayout", "direction": "LR" }
]
}
| Op | Fields | Effect |
|---|---|---|
addNode | node | Adds a node. Its key is made unique if it collides. Triggers a relayout unless you pin coordinates or pass relayout: false. |
updateNode | key, set | Changes label, subtitle, details, colour, type, size, position, font. |
moveNode | key, x, y | Repositions without relayout. |
removeNode | key | Removes the node and every edge touching it. |
addEdge | from, to, label, arrow | Connects two nodes. A duplicate (in either direction) is skipped with a warning. |
updateEdge | from, to, set | Changes label, arrow or ports. |
removeEdge | from, to | Removes the connection. |
setTitle | title | Renames the diagram. |
setArtboard | size, width, height, bgColor | Changes the page. |
relayout | direction | Re-runs auto-layout and re-points every connection. |
clear | — | Removes all nodes and edges, keeping the diagram record. |
Node references resolve by key first, then numeric id, then exact label text —
so { "op": "updateNode", "key": "PostgreSQL" } works even on a diagram drawn by hand
that has no keys yet.
"dryRun": true to see applied, warnings and the
resulting spec without writing anything. Worth doing before a destructive patch.
Generated documentation
Every diagram can describe itself. The generator walks the graph rather than the pixels
and produces Markdown containing: a summary naming the entry points, terminals and busiest
component; a component table grouped by family, with the details text as prose;
a connection table; the end-to-end paths traced from each entry point; annotations; and a
Mermaid version of the flowchart.
Documentation is written automatically on create, replace and patch. Fetch it in three formats:
| Request | Returns |
|---|---|
GET …/doc | Markdown (the canonical form) |
GET …/doc?format=html | A styled, printable page |
GET …/doc?format=json | Markdown plus the machine-readable graph |
GET …/doc?refresh=true | Regenerates from the current drawing first |
GET …/mermaid | Just the Mermaid flowchart source |
To write the documentation yourself instead — an agent explaining why the system is
shaped this way, which no generator can infer — POST …/doc with
{ "content": "# …", "summary": "…" }. Authored documents are flagged
generated: false and are not overwritten by later automatic regeneration unless
you ask for it.
To keep the generated body but add context, pass notes on any create, PUT or
PATCH — the text is inserted as a Notes section above the component tables.
In the browser, /docs/diagram/{uuid} renders the document for reading or printing to PDF.
Endpoint reference
Base URL /api/ai. Machine-readable index at
/api/ai/manifest.
Discovery
Capabilities, component types, artboard sizes, patch ops, endpoint list. No auth required.
JSON Schema for spec and patch bodies. No auth required.
Which user the credential resolves to, its scopes, and how many diagrams they own.
Diagrams
List your diagrams. Filters: ?q= title search, ?category=, ?limit= (max 500).
Create from { title, category, spec, notes }. Pass documentation: false to skip generating docs. Returns 201.
Read one. ?include=spec,data,doc,mermaid — defaults to spec.
The drawing as an editable spec — the form to read before patching.
Replace contents with a new spec. With only title/category, updates metadata and leaves the drawing untouched.
Apply ops. Supports dryRun and expectedUpdatedAt.
Delete the diagram and its documentation. Not recoverable.
Compile a spec and return the layout, warnings and thumbnail without saving.
Documentation
?format=md|html|json, ?refresh=true to regenerate first.
Regenerate, or replace with your own content.
Delete the stored document. The next read regenerates one.
Mermaid flowchart source. ?format=json to wrap it in JSON.
Every diagram of yours that has a stored document.
Organisation
List your categories.
Create one. Idempotent by name.
Recent automated changes to your diagrams — action, diagram, key used, timestamp.
Errors & concurrency
| Status | Meaning | What to do |
|---|---|---|
400 | Bad spec or patch. The body names the offending index and field. | Fix and resend — do not retry unchanged. |
401 | Missing, invalid or revoked key. | Check the X-API-Key header. |
403 | The key lacks the required scope. | Use a read/write key. |
404 | No such diagram for this user. | Check the UUID and the key's owner. |
409 | expectedUpdatedAt did not match — someone else changed it. | Re-read, rebase your ops, retry. |
429 | Rate limit exceeded. | Back off; the window is one minute. |
Errors are precise on purpose. An unknown node type is downgraded to server with a
warnings entry, but an edge pointing at a node that does not exist is a hard 400 —
silently dropping it would produce a diagram that looks fine and is wrong.
Several agents editing one diagram should pass expectedUpdatedAt (from the
updated_at you read) on every write. Without it, last write wins.
Instructions for an agent
Paste this into an agent's system prompt or tool description, with the key filled in.
You can draw and maintain system diagrams in eDraw.
Base URL: https://edraw.void.bd/api/ai
Auth: header X-API-Key: <key>
Discover the API with GET /manifest and GET /schema before your first write.
Creating a diagram
POST /diagrams { "title": "...", "spec": { "nodes": [...], "edges": [...] } }
- Nodes: { key, type, label, subtitle, details }
- Edges: { from, to, label } (from/to are node keys)
- Never send x/y. The server lays the diagram out.
- Put the concrete technology in `subtitle`, and the explanation in `details`
as one short bullet per line ("Owns the order lifecycle\nOnly writer to
the orders table"). Do not write a paragraph — every line becomes a
bullet, on the canvas and in the documentation.
Changing a diagram
1. GET /diagrams/{uuid}/spec - read the current nodes and edges
2. PATCH /diagrams/{uuid} { "ops": [...] }
Use ops (addNode, updateNode, removeNode, addEdge, updateEdge, removeEdge,
setTitle, relayout). Do not re-send the whole spec unless you intend to
discard everything, including edits a person made.
Add "dryRun": true first when the patch removes anything.
Documentation
Docs regenerate automatically on every write.
GET /diagrams/{uuid}/doc - Markdown
POST /diagrams/{uuid}/doc { "notes": "..." } - keep the generated body, add context
POST /diagrams/{uuid}/doc { "content": "..." } - replace with your own prose
Rules
- One diagram per subject. Check GET /diagrams before creating a near-duplicate.
- Label every edge with what actually crosses it (protocol, payload, trigger).
- Choose the type that matches reality: database, cache, queue, api,
loadbalancer, microservice, cdn, firewall, storage, cloud, server,
webapp, mobile, desktop, user.
- A 400 means your body is wrong. Read the message; do not retry unchanged.
Stored data format
What the editor actually saves, in case you need to read or write it directly
(?include=data, or data instead of spec on write):
{
"elements": [{
"id": 1, // numeric, unique within the diagram
"key": "api", // added by the API; stable handle, ignored by the editor
"type": "api",
"x": 80, "y": 120, "width": 140, "height": 80,
"text": "API Gateway",
"subtitle": "Kong",
"details": "…",
"color": "#F59E0B", "textColor": "#ffffff",
"layoutStyle": "compact",
"locked": true
}],
"connections": [{
"id": 1754600000000,
"from": 1, "to": 2, // element ids, not keys
"fromPort": "right", "toPort": "left",
"label": "gRPC",
"arrowType": "end"
}],
"artboards": [{
"id": 1, "name": "Artboard 1", "size": "A4-landscape",
"x": 0, "y": 0, "width": 1123, "height": 794, "bgColor": "#ffffff"
}],
"markerStrokes": [],
"panOffset": { "x": 0, "y": 0 },
"zoom": 1,
"meta": { "source": "ai-api", "direction": "LR", "generatedAt": "…" }
}
key and
meta stay attached after a person opens and saves the diagram.
Architecture
Node.js and Express, SQLite through better-sqlite3, Socket.IO for live share. The frontend is plain JavaScript against a single canvas — no build step, no framework.
| File | Role |
|---|---|
src/server.js | Express app, routes, sessions, Socket.IO |
src/database.js | Schema, migrations, every SQL query |
src/auth.js | Session guards |
src/app.js | The editor — canvas, tools, export, autosave |
src/ai-model.js | Spec compile/decompile, layered layout, patch engine |
src/ai-docs.js | Graph analysis, Markdown/Mermaid/HTML generation |
src/ai-thumb.js | SVG thumbnails for diagrams made without a browser |
src/ai-routes.js | The /api/ai router |
Tables
| Table | Holds |
|---|---|
users | Accounts; bcrypt hashes; role user or admin |
diagrams | UUID, owner, title, category, JSON data, thumbnail |
user_categories | Per-user category names |
api_keys | SHA-256 hash, prefix, scopes, last used, revoked |
diagram_docs | One document per diagram; Markdown plus summary |
ai_activity | Audit trail of every automated change |
visitors, active_sessions | Analytics for the admin panel |
Running & deploying
Locally
npm install
PORT=7411 npm start # http://localhost:7411
The database is created at src/edraw.db on first run, with an
admin account seeded if the users table is empty.
In production
The published copy runs from /published/edraw under systemd
(edraw.service) on port 7412, behind nginx, reachable at
edraw.void.bd over TLS and on the LAN at 192.168.31.132:7410.
# publish source, never the database
rsync -a --exclude edraw.db --exclude node_modules src/ /published/edraw/src/
sudo systemctl restart edraw
systemctl status edraw --no-pager
/published/edraw/src/edraw.db is the live database — every account, diagram and
API key. Never copy over it, never delete it. Back it up before any deploy.
eDraw