A tab's elements change through changesets, but a document holds more than elements: the cards on its Plan boards, the cells of its Sheets, the images it shows, its Illustrate pages. Each has its own endpoints, shown here with curl. The examples assume your API token is in $TOKEN and a document id in $DOC; paths are relative to https://livediagram.app/api, and /api/openapi.json has every field.
Every write here reaches anyone with the document open straight away, credited to you.
Plan Items
A card on a Plan board is an item, kept in the document's item store rather than on a tab, so one card can show on several boards. A board files its cards by status: each column has one, such as in-progress~k3f2. Read the boards, their statuses and the card types first:
curl https://livediagram.app/api/documents/$DOC/plan \
-H "Authorization: Bearer $TOKEN"
{
"boards": [
{
"tabId": "tab-1",
"tabName": "Sprint",
"elementId": "b1",
"title": "Sprint 12",
"kind": "board",
"types": null,
"columns": [
{ "status": "todo~a1b2", "name": "To Do" },
{ "status": "doing~c3d4", "name": "Doing", "wipLimit": 3 }
]
}
],
"statuses": [
{ "status": "todo~a1b2", "name": "To Do" },
{ "status": "doing~c3d4", "name": "Doing" }
],
"types": [
{ "id": "task", "label": "Task", "fields": ["title", "description", "status", "assignee", "…"] }
],
"itemTypesRev": 3
}
GET /documents/$DOC/items lists the items themselves with the store's revision ({ "items": [...], "rev": 41 }). Each item has an id, a type, a key (the number people say, #12, never reused) and its fields.
Make Cards
POST /documents/$DOC/items makes one card: a type, its fields (a title at least, up to 500 characters) and an optional place, the status to file it under and the card to sit after or before:
curl -X POST https://livediagram.app/api/documents/$DOC/items \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"type": "task",
"fields": { "title": "Fix login", "priority": "high", "labels": ["auth"], "due": "2026-11-02", "estimate": 3 },
"place": { "status": "todo~a1b2" }
}'
It answers 201 with { "item": …, "rev": 42 }. POST /documents/$DOC/items/bulk makes up to 200 in one write, in order: { "items": [ { "type": "task", "fields": { "title": "One" } }, … ] }. A document holds up to 2,000 items.
The fields every card understands are title, description, status, assignee (a person { "id", "name", "color" }), priority (urgent, high, medium or low), color, labels (up to 12), estimate (0 to 999), start and due (YYYY-MM-DD), checklist, archived and flagged. A card type's custom fields go under their ids, such as f-severity. votes and comments are written only by voting and by the comment endpoints.
Change and Move Cards
# Change one card: set some fields, clear others, optionally change its type
curl -X POST https://livediagram.app/api/documents/$DOC/items/$ITEM \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "set": { "priority": "urgent" }, "clear": ["due"] }'
# Move it to another column, after another card, and assign it in the same write
curl -X POST https://livediagram.app/api/documents/$DOC/items/$ITEM/move \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "status": "doing~c3d4", "after": "'"$OTHER"'", "set": { "assignee": { "id": "u1", "name": "Sam", "color": "#2563eb" } } }'
# Change up to 200 cards at once: each is checked first, so one refusal changes nothing
curl -X POST https://livediagram.app/api/documents/$DOC/items/patches \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "items": [ { "id": "'"$A"'", "set": { "flagged": true } }, { "id": "'"$B"'", "clear": ["labels"] } ] }'
A move takes a status and after or before (after wins; neither puts the card at the end of the column). Its set and clear reach only the fields a swimlane can change (assignee, priority, labels, estimate, start, due, a custom field, or archived). A change never clears a card's title. DELETE /documents/$DOC/items/$ITEM deletes one card.
POST /documents/$DOC/items/tally adds a session vote's dots to up to 200 cards, { "items": [ { "id": "…", "votes": { "<voter id>": 2 } } ] }, as the editor does when a vote ends. A card deleted meanwhile is skipped.
Refusals are 400 with a code such as title_required, field_value_invalid, type_invalid, place_invalid or status_excluded (the card's type leaves that status out), 404 item_not_found, 409 item_busy (try again) or 413 items_full.
Card Types
A document's card types are stored whole. To change them, send the full catalogue back with the revision you read (itemTypesRev from /plan):
curl -X PUT https://livediagram.app/api/documents/$DOC/item-types \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"expectedRev": 3,
"itemTypes": {
"version": 1,
"types": [
{ "id": "bug", "label": "Bug", "newTitle": "New bug", "glyph": "task", "color": "#dc2626",
"fields": ["title", "description", "status", "assignee", "priority", "f-severity", "comments"],
"custom": [{ "id": "f-severity", "label": "Severity", "kind": "choice", "options": ["S1", "S2", "S3"] }] }
]
}
}'
Up to 32 types. A custom field's kind is text, longtext, number, date, checkbox, link, choice or card, and its id starts f-. "itemTypes": null goes back to the default types (Project, Task, Note, Idea, Action). If someone changed the types since you read them, the answer is 409 item_types_stale with the stored catalogue and revision, to apply your change to again. This needs edit access to the whole document.
The CLI (item add, item set, item move, type ls) and the MCP server's change_items and change_card_types do all of this by column and type names, which is easier for an agent than status ids.
Sheets
GET /documents/$DOC/sheets returns every Sheet with its cells (?tabId= for one tab's, ?ids= for named ones). A Sheet addresses rows and columns by id, not by letter, so inserting a row never breaks a reference: its layout lists the row ids top to bottom and the column ids left to right, and A1 is the first column id with the first row id.
{
"sheets": [
{
"id": "k7Qm2xPaR4tZ",
"tabId": "tab-1",
"title": "Costs",
"rev": 7,
"layout": { "rows": ["r1x", "r2y", "…"], "cols": ["c1p", "c2q", "…"] },
"cells": [
{ "r": "r1x", "c": "c1p", "i": { "s": "Item" } },
{ "r": "r1x", "c": "c2q", "i": { "s": "Cost" } }
]
}
]
}
Change cells with a write. Each cell change names its row and column ids and an input: { "n": 1200 } for a number, { "s": "Hosting" } for text, { "b": true } for a checkbox value, or null to clear it. An f patch formats it ("b": true for bold, "nf": "currency", "bg" and "fc" for colours):
curl -X POST https://livediagram.app/api/documents/$DOC/sheets/$SHEET/writes \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"write": {
"kind": "cells",
"cells": [
{ "r": "r2y", "c": "c1p", "i": { "s": "Hosting" } },
{ "r": "r2y", "c": "c2q", "i": { "n": 1200 }, "f": { "nf": "currency" } },
{ "r": "r1x", "c": "c2q", "f": { "b": true } }
]
}
}'
It answers with the write as it landed, the sheet's new rev and the stored state of every cell it touched. The other two writes are { "kind": "title", "title": "Costs 2027" } and { "kind": "layout", "changes": [ … ] }, whose changes insert, delete, move, size, hide, freeze, merge or filter rows and columns, such as { "k": "freeze", "rows": 1 }.
Formulas are stored with their references by row and column id, which is awkward to write by hand.
To write formulas, or anything by A1, use livediagram sheet set "Budget" Costs B2 1200 =B2*12 or
the MCP server's change_sheet, which work out the ids for you.
A Sheet shows on a tab through a Sheet element. livediagram sheet add (or the MCP server's add_sheet) makes the sheet and places its element in one go. Over REST, POST /documents/$DOC/sheets with { "tabId": "…", "title": "Costs" } makes the sheet (titles are unique on a tab), and a changeset adds its element: { "op": "add", "element": { "type": "shape", "shape": "plan-sheet", "x": 0, "y": 0, "width": 960, "height": 560, "planSheet": { "sheetId": "…" } } }. See Sheets for what people see.
Images
Upload the raw bytes of a PNG, JPEG, WebP or GIF, up to 10 MB, with its pixel size in two headers:
curl -X POST https://livediagram.app/api/images \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: image/png" \
-H "X-Image-Width: 1200" -H "X-Image-Height: 800" \
-H "X-Image-Original-Name: architecture.png" \
--data-binary @architecture.png
{
"image": {
"id": "9f2c…",
"contentType": "image/png",
"byteSize": 48211,
"width": 1200,
"height": 800,
"originalName": "architecture.png",
"createdAt": 1760180000000
},
"deduped": false
}
Uploading the same bytes again answers the image you already have, with "deduped": true. Then put it on a tab with a changeset that adds a whole image element:
curl -X POST https://livediagram.app/api/documents/$DOC/tabs/$TAB/changesets \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "operations": [ { "op": "add", "element": { "type": "image", "imageId": "'"$IMAGE"'", "x": 0, "y": 0, "width": 600, "height": 400, "naturalWidth": 1200, "naturalHeight": 800, "alt": "Architecture overview" } } ] }'
GET /images lists your uploads, GET /images/usage maps each to the documents that use it, GET /images/{id} downloads one, and DELETE /images/{id} removes it from your gallery. Refusals are 415 unsupported_type, 413 file_too_large, and on a host that sets a gallery limit, 403 gallery_full.
Illustrate Pages and Articles
POST /documents/$DOC/tabs/$TAB/illustrate changes a tab's Illustrate pages, up to 50 changes as one edit, or writes an article from Markdown. A tab in another mode is switched into Illustrate by the same edit; a locked tab or an Event Storming board is refused.
# Add two slides and rename the first page
curl -X POST https://livediagram.app/api/documents/$DOC/tabs/$TAB/illustrate \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "pages": [
{ "op": "add", "kind": "slide", "name": "Roadmap" },
{ "op": "add", "kind": "slide", "background": { "gradient": ["#1e3a8a", "#7c3aed"] } },
{ "op": "set", "page": 1, "name": "Cover" }
] }'
# Write an article: the editor flows it onto pages
curl -X POST https://livediagram.app/api/documents/$DOC/tabs/$TAB/illustrate \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "article": { "markdown": "# Release notes\n\nWhat changed this month.", "size": "a4" } }'
A page change is add (a kind of infographic, slide or logo, with an optional size, orientation, name, background, layout and at), set, layout, move, duplicate or delete; a page is named by its id, its place in the row (from 1) or its name. An article write takes mode replace (the default) or append, and article to name an existing one. The answer lists every page with its rectangle on the canvas, which is where a changeset puts elements to land them on that page.
A Tab as a Picture
curl https://livediagram.app/api/documents/$DOC/tabs/$TAB/render.svg \
-H "Authorization: Bearer $TOKEN" -o overview.svg
The tab drawn by the same renderer the CLI's tab render and pull --svg use, as image/svg+xml, with images inlined when the host stores them. It is never cached, so it always shows the tab now. For an image to embed somewhere public, use a live image instead.
Copy a Document
curl -X POST https://livediagram.app/api/documents/$DOC/copy \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "name": "Service map (2027 draft)" }'
Makes a copy in your own files and answers 201 with the new document. Leave out name for Copy of <name>. Anything you can read can be copied; share links are not copied with it.
Shape Libraries and Custom Themes
Your shape libraries and custom themes belong to you, not to a document.
# A shape library: each item is a reusable shape, its elements placed from its top-left corner
curl -X POST https://livediagram.app/api/shape-libraries \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "id": "'"$(uuidgen)"'", "name": "Network kit", "source": "drawio",
"items": [ { "id": "'"$(uuidgen)"'", "title": "Router", "width": 120, "height": 60,
"elements": [ { "id": "r1", "type": "shape", "shape": "square", "x": 0, "y": 0, "width": 120, "height": 60, "label": "Router" } ] } ] }'
# A custom theme
curl -X POST https://livediagram.app/api/custom-themes \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "id": "custom:'"$(uuidgen)"'", "name": "Night shift",
"definition": { "backgroundColor": "#0f172a", "backgroundPattern": "grid", "patternColor": "#1e293b",
"elementFill": "#1e293b", "elementStroke": "#38bdf8", "elementText": "#f8fafc" } }'
A library holds up to 1,000 items and you can keep 100; a name you already use gets a number added. PUT /shape-libraries/{id} renames one or replaces its items, and DELETE removes it (shapes already placed stay in their documents). A custom theme's id starts custom:, and a tab uses it by that id. PUT /custom-themes/{id} changes its name or definition, DELETE removes it, and a palette you send must not be empty. GET on either path lists yours.
Was this article helpful?