# Archboard document format

Every Archboard diagram is a versioned JSON document (version 2) with a published JSON Schema, so other tools can read, validate, and generate it. JSON import and export are on every plan.

## JSON Schemas

- Diagram document v2: https://getarchboard.app/schemas/archboard-document-v2.json
- Clipboard payload v1: https://getarchboard.app/schemas/archboard-clipboard-v1.json
- Edit operations v1: https://getarchboard.app/schemas/archboard-ops-v1.json

The schemas are JSON Schema draft 2020-12, generated from the validators Archboard itself uses. Fields with a default are optional when you write a document.

## Versioning policy

- A breaking change gets a new `version` number and a new schema file. Archboard migrates older documents when it reads them, so files you exported keep opening.
- Optional fields can be added within version 2; the schema at the same URL is updated. Readers must ignore fields they do not know.
- Archboard’s own validators stay strict: import rejects fields the current schema does not list. When you write a document, use only fields from the schema.
- `props` of nodes and edges is an open record: a key Archboard does not use is kept but has no effect on the drawing. The MCP write tools warn about such keys and about values a key does not accept.
- Edit operations (ops v1) and the clipboard payload (v1) follow the same policy.

## Document

- `version`: the format version.
- `meta`: `title`, optional `updatedAt` and `canvasSettings`. In `canvasSettings`, `packVisibility` and `styleDefaults` are shared by everyone; `showGrid`, `gridSize`, `gridStyle`, `snapToGrid`, `snapToObjects`, `smartGuides`, and `showRulers` are the view settings of the person who exported the file and are not shared in live editing.
- `viewport`: `x`, `y`, `zoom` (0.01–10): the pan and zoom of the person who exported the file.
- `nodes[]`: `id`, `type` (`<pack>.<kind>`, for example `k8s.deployment` or `flowchart.decision`), `x`, `y` (top-left, absolute world pixels, also inside containers), `w`, `h`, `z`, `props` (type-specific; `label` for the visible text), optional `parentId` and `caption`.
- Container size: a container (`get_node_type` reports `container: true`) keeps its `x`, `y`, `w`, and `h` only when `props.autoHeight` is `false`. When it is `true` or missing, Archboard fits the container around its children, when the diagram opens and after changes, moving and resizing it. Write `"autoHeight": false` to keep the size you set, as in the example below.
- `edges[]`: `id`, `source` / `target` node ids with optional `sourceAttach` / `targetAttach`, or free ends `sourcePoint` / `targetPoint`; `type` (`bezier`, `straight`, `orthogonal`), `z`, `props` (`label`, arrows, stroke, animation, waypoints…; every key and value is listed under “Edge style” in https://getarchboard.app/developers/mcp.md).
- `assets[]`: embedded images and files (`id`, `kind`, `url`, usually a data URL). Nodes refer to them with `props.assetId` or `"asset:<id>"`.

Rules the schema cannot express: ids are unique across nodes, edges, and assets together (an asset cannot share an id with a node), and an id is not empty, does not start with `$`, and is at most 1024 characters; `parentId` must not form a cycle (a `parentId` that names no node is treated as a root node); an edge end is either attached or free, never both. Live editing and the MCP tools reject ids that break these rules (`id_taken`, `invalid_op`); a stored document that breaks them is repaired when it opens: ids that collide or break a rule are renamed and references follow (a node keeps its id, so an edge `api` next to a node `api` becomes `api~1`). Tools that sync by id should follow these rules. Use `search_node_types` / `get_node_type` on the MCP server, or the node catalog in the editor, for the types and their props.

### Example

```json
{
  "version": 2,
  "meta": {
    "title": "Checkout service"
  },
  "viewport": {
    "x": 0,
    "y": 0,
    "zoom": 1
  },
  "nodes": [
    {
      "id": "ns",
      "type": "k8s.namespace",
      "x": 0,
      "y": 0,
      "w": 480,
      "h": 264,
      "z": 0,
      "props": {
        "label": "checkout",
        "autoHeight": false
      }
    },
    {
      "id": "api",
      "type": "k8s.deployment",
      "x": 48,
      "y": 72,
      "w": 216,
      "h": 72,
      "z": 1,
      "parentId": "ns",
      "props": {
        "label": "checkout-api",
        "replicas": 3,
        "namespace": "checkout"
      }
    },
    {
      "id": "svc",
      "type": "k8s.service",
      "x": 48,
      "y": 168,
      "w": 216,
      "h": 48,
      "z": 1,
      "parentId": "ns",
      "props": {
        "label": "checkout",
        "namespace": "checkout"
      }
    }
  ],
  "edges": [
    {
      "id": "e1",
      "source": "svc",
      "target": "api",
      "sourceAttach": {
        "kind": "side",
        "side": "top",
        "t": 0.5
      },
      "targetAttach": {
        "kind": "side",
        "side": "bottom",
        "t": 0.5
      },
      "type": "orthogonal",
      "z": 2,
      "props": {
        "label": "HTTP 8080"
      }
    }
  ],
  "assets": []
}
```

## Edit operations (ops v1)

The `ops` input of the MCP `apply_ops` tool: up to 10 000 operations applied as one change.

- `node.add` / `edge.add` / `asset.add` carry a full record; the id must be new.
- `node.set` / `edge.set` carry `set` and `unset`. Each top-level field and each `props` key is its own unit, so two people changing different fields of the same node both win.
- `node.remove` removes the node, its children, and every edge attached to them. `meta.set` changes the title.
- Ids that start with `$` are temporary; the server returns the real ids in `idMap`. References (`parentId`, edge ends, `props.assetId`, `"asset:<id>"`) are resolved at the end of the list, so they may point to a record added later. `set` and `remove` operations are applied in list order, so add a record before the operations that change or remove it: a `set` whose target is not there yet is dropped (`effects.dropped`) and such a `remove` does nothing.

```json
[
  {
    "op": "node.add",
    "node": {
      "id": "$db",
      "type": "k8s.statefulset",
      "x": 288,
      "y": 72,
      "w": 168,
      "h": 72,
      "parentId": "ns",
      "props": {
        "label": "postgres"
      }
    }
  },
  {
    "op": "edge.add",
    "edge": {
      "id": "$e2",
      "source": "api",
      "target": "$db",
      "props": {
        "label": "SQL"
      }
    }
  },
  {
    "op": "node.set",
    "id": "api",
    "set": {
      "props": {
        "replicas": 5
      }
    }
  }
]
```

## Clipboard payload (v1)

When you copy a selection, Archboard puts text on the clipboard: a first line with the MIME type `application/x-archboard-nodes+json`, then the JSON payload with `mime: "application/x-archboard-nodes+json"`, `version: 1`, and the copied `nodes`, `edges`, and `assets` (same records as the document). Remove the first line before you parse the JSON; the schema describes the payload only. Pasting into Archboard accepts the payload with or without that first line and recreates the selection.

MCP server and agent guide: https://getarchboard.app/developers/mcp.md
