These examples use curl against the hosted service. Each request carries your API token; the examples assume you have put it in a shell variable:
export TOKEN=lvd_your_token_here
Paths are relative to https://livediagram.app/api. For the exact request and response shapes of every field, see the OpenAPI reference at /api/openapi.json.
Old paths. These routes were called /api/diagrams… until documents got their name. Scripts that still call the old paths keep working until 30 April 2027, with a Deprecation header on every answer; move them to /api/documents… before then.
List your documents
A document list returns the documents in your personal library (documents in a team library are not included), as metadata only, not the tab contents:
curl https://livediagram.app/api/documents \
-H "Authorization: Bearer $TOKEN"
{
"documents": [{ "id": "abc123", "name": "Service map", "folderId": null, "shareable": false }]
}
Read a document and a tab
Fetching one document returns its metadata and a list of tab summaries, but still not the elements on each tab:
curl https://livediagram.app/api/documents/abc123 \
-H "Authorization: Bearer $TOKEN"
To get the actual contents of a tab, request it by id. This keeps listing a big document cheap:
curl https://livediagram.app/api/documents/abc123/tabs/tab-1 \
-H "Authorization: Bearer $TOKEN"
{
"tab": {
"id": "tab-1",
"name": "Overview",
"rev": 7,
"elements": [/* ... */]
}
}
Read a tab as text
The element JSON carries every coordinate and colour. When you want what a tab means, which is usually what a script or an AI model needs, ask for a view instead. The outline gives one line per element, nested by frame, with its arrows on the same line:
curl "https://livediagram.app/api/documents/abc123/tabs/tab-1?view=outline" \
-H "Authorization: Bearer $TOKEN"
tab tab-1 "Overview" · 3 elements: 2 boxes, 1 arrow · rev 7
square n1 "Web" → n2
square n2 "API"
Each element is named by a short ref: its id when that is short and readable, otherwise the first few characters of it. Anywhere the API takes an element, a ref, any unique start of the id or the full id all work. The first line ends with the tab's revision.
Other views answer other questions:
view=graphlists what connects to what.view=layoutgives positions and sizes; addcoarse=1for rows instead.view=commentsshows the open comment threads in full; addall=1for resolved ones too.view=show&ref=n1prints one element with every field and its arrows both ways.view=find&q=apilists the elements whose text containsapi.
view=lint checks how the tab is drawn: boxes that overlap, arrows that cross or run behind boxes, labels too long for their box. One summary line, then one finding a line, each ending with a fix you can send as a changeset:
5 crossings · 2 behind · 0 overlaps · 1317×1196 → 3 warnings, 6 info
W arrow-behind-box orders→bus passes behind pay fix: set orders->bus line=angled
Every changeset's last line carries the same verdict, such as lint clean or lint 1 warning, beside the new revision and the revert command. A changeset that changes nothing answers nothing changed.
Add budget=2000 to fit any view except lint to about 2,000 tokens. The view then leaves out detail in a fixed order, and its last line says what it left out and how to see it. Add json=1 for the same view as JSON. To see every tab of a document at a glance, ask the document itself: GET /documents/abc123?view=overview.
Create a document
POST /documents creates a document. You supply the id, a name, and optionally the tabs to seed it with. Nodes are shape elements with a position and size; this creates one tab with two boxes. Ids are shared across all of livediagram, not just your account, so use a fresh UUID for the document, each tab and each folder; an id someone else already holds is refused or swapped for a new one:
DOC=$(uuidgen) TAB=$(uuidgen) FOLDER=$(uuidgen)
curl -X POST https://livediagram.app/api/documents \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"id": "'"$DOC"'",
"name": "Service map",
"tabs": [
{
"id": "'"$TAB"'",
"name": "Overview",
"elements": [
{ "id": "n1", "type": "shape", "shape": "square", "x": 0, "y": 0, "width": 160, "height": 80, "label": "Web" },
{ "id": "n2", "type": "shape", "shape": "square", "x": 0, "y": 160, "width": 160, "height": 80, "label": "API" }
]
}
]
}'
Arrows, text, images, and the rest of the element types follow the same pattern with their own fields. The OpenAPI document's Tab and Element schemas describe every one.
Update a tab
Scripts and agents change a tab with a changeset: a short list of edit operations, applied together or not at all. Anyone with the document open sees the change arrive straight away, it is credited to you, and it can be undone as one unit for 30 days. Changes people make in the editor at the same time are kept.
curl -X POST https://livediagram.app/api/documents/$DOC/tabs/$TAB/changesets \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"summary": "rename the API box, add a database",
"operations": [
{ "op": "set", "target": "n2", "fields": { "label": "Orders API" } },
{ "op": "add", "element": { "id": "n3", "type": "shape", "shape": "cylinder", "x": 0, "y": 320, "width": 160, "height": 80, "label": "Orders DB" } },
{ "op": "rm", "target": "n1" }
]
}'
set changes only the fields you name, add takes a whole element, and rm removes an element (here the Web box) along with any arrow pinned to it. The answer lists what changed, the changeset's id and the tab's new revision. Add ?dryRun=1 to see the answer without changing anything.
The same operations read well as text, one a line, which is easier for a script or an agent to write. Send them as a string:
curl -X POST https://livediagram.app/api/documents/$DOC/tabs/$TAB/changesets \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"operations": "set n2 label=\"Orders API\"\nadd cylinder id=db label=\"Orders DB\" below:n2\nconnect n2 -> db"
}'
Elements are named by the refs a text view prints, or picked by what they are: "Orders API" by label, type:sticky, in:f2 for what a frame holds, n3->n4 for an arrow. Edit Operations Reference lists every operation, selector, placement and flag. Besides add, set and rm there are move, connect, rewire, insert (a step between two connected ones, making room for it), wrap and unwrap (frames and lanes), order, layout, and test, which refuses the changeset unless an element still holds the values you read. New boxes are sized for their label and placed beside what you name, and the answer has one line per element changed:
~ n2 label "API"→"Orders API"
+ db cylinder "Orders DB" @14,270 113×140
+ arrow n2→db
To rebuild a whole tab, send "replace" instead of "operations", with elements (or a graph, mermaid text or a template). A replace on a tab id the document doesn't have yet creates that tab.
Every tab read returns the tab's rev. Send it back as "base": { "rev": 7 } and the changeset is
refused with 409 changeset_conflict if an element it touches changed since you read it, so
nothing you didn't see gets overwritten. An element somebody has selected in the editor is left
alone too: the changeset is refused with 409 elements_held, naming who holds it.
To rename a tab, PUT its name: PUT /documents/$DOC/tabs/$TAB/name with { "name": "Overview" }. The whole-tab PUT the editor saves with is not open to tokens; it answers 405 use_changesets.
GET /documents/$DOC/changesets lists the recent changesets, and POST /documents/$DOC/changesets/<id>/revert undoes one, leaving any element somebody changed since.
Plan cards, Sheet cells, images and Illustrate pages have endpoints of their own: see Plan Items, Sheets and Images.
Organise into folders
Create a folder, then move a document into it:
curl -X POST https://livediagram.app/api/folders \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "id": "'"$FOLDER"'", "name": "Infrastructure" }'
curl -X PUT https://livediagram.app/api/documents/$DOC/folder \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "folderId": "'"$FOLDER"'" }'
Delete and restore
DELETE /documents/$DOC moves the document to the Trash, where it waits for 30 days, and answers 204. While it is there, reading or changing it answers 410 document_trashed. Add ?permanent=true to delete it for good straight away.
curl -X DELETE https://livediagram.app/api/documents/$DOC \
-H "Authorization: Bearer $TOKEN"
# What you can restore: your own Trash and every team's you belong to
curl https://livediagram.app/api/trash \
-H "Authorization: Bearer $TOKEN"
curl -X POST https://livediagram.app/api/trash/$DOC/restore \
-H "Authorization: Bearer $TOKEN"
DELETE /trash/$DOC deletes one trashed document for good, and DELETE /trash empties your personal Trash (?team=<id> empties a team's). A read-only token can list the Trash but not restore or purge.
Share a document
Create a share link with a role of view, participate or edit:
curl -X POST https://livediagram.app/api/documents/$DOC/share \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "role": "view" }'
The response includes the share code to build a link from. See Sharing and Embeds for what the different roles allow.
Send the role explicitly. Omitting it does not mean "view": a body with no role mints an
edit link, which is the app's own default. An unrecognised role is rejected outright rather
than quietly falling back, so a typo fails loudly, but an absent one does not.
When a call fails, the status code and a small { "error": "..." } body tell you why. See
Errors and Rate Limits.
Was this article helpful?