Working with Designs

Copy-pasteable examples for the calls you will reach for most.

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=lwt_your_token_here

Paths are relative to https://livedesign.app/api. For the exact request and response shapes of every field, see the OpenAPI reference at /api/openapi.json.

List your designs

A design list returns the designs in your personal library (designs in a team library are not included), as metadata only, not the tab contents:

curl https://livedesign.app/api/designs \
  -H "Authorization: Bearer $TOKEN"
{
  "designs": [{ "id": "abc123", "name": "Service map", "folderId": null, "shareable": false }]
}

Read a design and a tab

Fetching one design returns its metadata and a list of tab summaries, but still not the elements on each tab:

curl https://livedesign.app/api/designs/abc123 \
  -H "Authorization: Bearer $TOKEN"

To get the actual contents of a tab, request it by id. This keeps listing a big design cheap:

curl https://livedesign.app/api/designs/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://livedesign.app/api/designs/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=graph lists what connects to what.
  • view=layout gives positions and sizes; add coarse=1 for rows instead.
  • view=comments shows the open comment threads in full; add all=1 for resolved ones too.
  • view=show&ref=n1 prints one element with every field and its arrows both ways.
  • view=find&q=api lists the elements whose text contains api.

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 design at a glance, ask the design itself: GET /designs/abc123?view=overview.

Create a design

POST /designs creates a design. 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 livedesign, not just your account, so use a fresh UUID for the design and each tab; an id someone else already holds is refused or swapped for a new one:

DOC=$(uuidgen) TAB=$(uuidgen)

curl -X POST https://livedesign.app/api/designs \
  -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 design'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 design 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://livedesign.app/api/designs/$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://livedesign.app/api/designs/$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), 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 template). A replace on a tab id the design 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 /designs/$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 /designs/$DOC/changesets lists the recent changesets, and POST /designs/$DOC/changesets/<id>/revert undoes one, leaving any element somebody changed since.

Images and pages have endpoints of their own: see Images, Pages and More.

Organise into folders

List your folders, or make one with POST /api/folders ({ "name": "Screens" }, plus "parentId" to make it inside another), then create a design straight into one by naming its id ($FOLDER below) as folderId (or a team's library with teamId). Renaming and moving folders, and moving a design you already have, happens in the Explorer.

curl https://livedesign.app/api/folders \
  -H "Authorization: Bearer $TOKEN"

curl -X POST https://livedesign.app/api/designs \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{ "id": "'"$(uuidgen)"'", "name": "Infrastructure", "folderId": "'"$FOLDER"'" }'

Delete and restore

DELETE /designs/$DOC moves the design to the Trash, where it waits for 30 days, and answers 204. While it is there, reading or changing it answers 410 design_trashed. Add ?permanent=true to delete it for good straight away.

curl -X DELETE https://livedesign.app/api/designs/$DOC \
  -H "Authorization: Bearer $TOKEN"

# What you can restore: your own Trash and every team's you belong to
curl https://livedesign.app/api/trash \
  -H "Authorization: Bearer $TOKEN"

curl -X POST https://livedesign.app/api/trash/$DOC/restore \
  -H "Authorization: Bearer $TOKEN"

DELETE /trash/$DOC deletes one trashed design for good, and Empty Trash in the Explorer empties a whole Trash at once. A read-only token can list the Trash but not restore or purge.

Share a design

Create a share link with a role of view, participate or edit:

curl -X POST https://livedesign.app/api/designs/$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?