---
title: "Save a diagram"
method: POST
path: "/v1/diagrams"
tags: ["Diagrams"]
---

# Save a diagram

`POST /v1/diagrams`

## Request

- Base URL: `https://api.zindex.ai`
- URL: `https://api.zindex.ai/v1/diagrams`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-API-Key`

Saves a new diagram from a DSP scene document. Zindex mints the ID (any scene.id sent is ignored). key is your own name for it, unique among the workspace's live diagrams: repeating your own create with the same key and content returns the existing diagram (200, created: false) instead of a copy, so retries are safe. Every version is kept until you delete the diagram or its workspace is deleted. Send Mermaid text as mermaid instead of scene to import it.

## Headers

- `X-Zindex-Client` string

## Request body

- object: Send exactly one of scene or mermaid.
  - `scene` SceneDocument: A DSP scene document containing the scene definition, elements, and optional styles, constraints, palette, layoutStrategy, diagramFamily, and extensions.
    - `schemaVersion` '0.1', required
    - `scene` object, required: Scene metadata and canvas configuration. Canvas dimensions are a minimum per axis: when content exceeds the declared dimensions, the engine auto-extends the canvas to fit. The engine never silently clips. When the engine lays out every node (a new diagram whose nodes have no positions, autoLayout without elements, or a new layoutStrategy or diagramFamily, unless members of a pool, lane or fragment with its own layout, or of a frame in an org chart or network, keep their positions), it also auto-spreads the nodes around their centroid to fill a declared canvas larger than node spacing needs, and caps an axis declared more than 30% larger than the comfortable centred-content size at content x 1.4. This is the only path where the engine reduces the canvas below a declared dimension, and the size it picks is saved with the diagram. Supply absolute coordinates on any node of a new diagram to keep the declared size. After the first save nodes keep their positions, so a larger canvas set with updateScene adds room on the right and at the bottom; send autoLayout to spread the nodes into it. Neither growing nor tightening comes back as a warning.
      - `id` string
      - `title` string
      - `description` string
      - `brief` string: Context kept with the diagram for the next agent: what it shows, conventions to keep, decisions made. Versioned with the diagram.
      - `units` 'px'
      - `canvas` object: Canvas dimensions. Both width and height are a minimum: the engine grows past them to fit content. The engine may also tighten either axis BELOW declared when it lays out every node (a new diagram whose nodes have no positions, autoLayout without elements, or a new layoutStrategy or diagramFamily, unless members of a pool, lane or fragment with its own layout, or of a frame in an org chart or network, keep their positions) AND the declared dimension on that axis exceeds the comfortable centred-content size by more than 30% - to avoid rendering with excessive whitespace. The size it picks is saved with the diagram. Neither change comes back as a warning. width/height are optional - the canonical scene.schema.json marks them so, and the runtime computes content-derived dimensions when they are absent.
        - `width` number
        - `height` number
        - `background` string: CSS color value (hex, rgb, or rgba). When set to a dark color and no render `theme` is passed, the renderer auto-selects the dark theme so strokes / labels remain legible. Use `palette.background` to override the painted rect color without affecting theme selection.
    - `elements` Element[], required: Array of scene elements (nodes, edges, groups, frames, text, images, guides)
      - `id` string, required: Unique element identifier
      - `kind` 'node' | 'edge' | 'group' | 'frame' | 'text' | 'image' | 'guide', required: Element type
      - `nodeType` 'generic' | 'service' | 'database' | 'queue' | 'actor' | 'note' | 'decision' | 'document' | 'process' | 'container' | 'externalSystem' | 'storage' | 'objectStorage' | 'event' | 'iconNode' | 'workflow.start' | 'workflow.end' | 'workflow.task' | 'workflow.decision' | 'workflow.gateway' | 'workflow.gatewayParallel' | 'workflow.gatewayInclusive' | 'workflow.gatewayEvent' | 'workflow.subprocess' | 'workflow.timer' | 'workflow.error' | 'workflow.intermediateTimer' | 'workflow.intermediateMessage' | 'workflow.intermediateError' | 'workflow.intermediateThrowMessage' | 'workflow.terminate' | 'workflow.messageStart' | 'workflow.messageEnd' | 'er.entity' | 'er.weakEntity' | 'er.attribute' | 'er.relationship' | 'sequence.lifeline' | 'sequence.activation' | 'sequence.actor' | 'sequence.note' | 'network.server' | 'network.router' | 'network.switch' | 'network.firewall' | 'network.cloud' | 'network.client' | 'org.person' | 'org.role' | 'org.department' | 'uiflow.screen' | 'uiflow.modal' | 'uiflow.entry' | 'uiflow.exit': Semantic node type. Architecture-family types: generic, service, database, queue, actor, note, decision, document, process, container, externalSystem, storage, objectStorage, event, iconNode. Family-namespaced types (workflow.*, er.*, sequence.*, network.*, org.*, uiflow.*) require the corresponding diagramFamily on the scene. The same values as https://zindex.ai/schemas/scene.schema.json#/$defs/nodeType.
      - `edgeType` 'default' | 'workflow.sequenceFlow' | 'workflow.messageFlow' | 'er.relationship' | 'er.identifying' | 'sequence.message' | 'sequence.reply' | 'sequence.create' | 'sequence.async' | 'sequence.destroy' | 'network.dataFlow' | 'network.controlFlow' | 'org.directReport' | 'org.dottedLine' | 'uiflow.navigate' | 'uiflow.back' | 'uiflow.action': Family-specific edge type for typed connection semantics. The same values as edgeType in https://zindex.ai/schemas/scene.schema.json.
      - `shape` 'rect' | 'roundedRect' | 'ellipse' | 'diamond' | 'cylinder' | 'bucket' | 'pill' | 'hexagon' | 'parallelogram' | 'cloud' | 'box3d' | 'textBox' | 'iconBox': Visual shape for nodes
      - `label` string: Display label
      - `visible` boolean: false hides the element: it keeps its space but isn't drawn, nor any edge with an end at it. A hidden frame hides only its box and title, a hidden group only its title.
      - `layout` union: Position and size, as in scene.schema.json: mode absolute with x, y, width and height, or mode relative with a place clause such as { "rightOf": "api", "gap": 40 }. Leave it out and the engine places the node next to its neighbours, around the nodes that have a layout, without moving them; layoutStrategy on the scene picks how. After the first save the engine records the position here and keeps it, unless a node that grows needs the room and pushes this one, an edit adds or deletes a frame round it and keeping it would leave frames overlapping or a frame holding a node that isn't its member, or an added order constraint finds its two nodes the wrong way round; the autoLayout op, and a new or removed layoutStrategy or diagramFamily, place nodes again. Sequence diagrams are laid out from their messages, so their nodes stay at x 0, y 0. A layout at exactly x 0, y 0 counts as not placed.
        - AbsoluteLayout
          - `mode` 'absolute', required
          - `x` number, required
          - `y` number, required
          - `width` number, required
          - `height` number, required
          - `rotation` number: Kept with the layout. The reference engine doesn't rotate anything.
          - `autoSize` 'content' | 'none': 'content' sizes a node whose label wraps (textStyle.wrap 'word' or 'character') to its text: it keeps this width and gets at least the height its wrapped label and icon need, the height a TEXT_OVERFLOW warning gives, growing down from its top edge. The height here is the minimum. Nodes it grows into move down to make room, keeping the node spacing from it. No effect on a label that doesn't wrap (the engine already grows that node to fit it) or on nodes that lay out their own text: org chart people and roles, UI flow screens and modals, ER tables with columns, sequence nodes, BPMN events and gateways, UI flow entry and exit points.
        - RelativeLayout
          - `mode` 'relative', required
          - `place` union, required: Where to put the element: exactly one of rightOf, leftOf, above, below or inside, naming another element's ID.
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
          - `preferredSize` object
            - `width` number, required
            - `height` number, required
          - `rotation` number: Kept with the layout. The reference engine doesn't rotate anything.
      - `from` object: Edge source (for kind=edge). elementId may reference any element kind - typically a node, but frame and group IDs are also valid targets. An edge to a frame terminates at the frame's border using cardinal anchors; useful for abstract group dependencies (e.g. an edge to a 'Regional Services' sidebar rather than to a specific service inside). On entityRelationship diagrams, set `column` to anchor the edge at a specific column row inside the entity. Set `cardinality` for an explicit Crow's Foot glyph; when omitted, the engine infers it from the resolved column's pk/fk/unique/nullable metadata.
        - `elementId` string, required
        - `portId` string
        - `anchor` object: Optional explicit anchor point on the element's face. Mutually exclusive with portId.
        - `column` string: Optional column-row anchor for ER entities. Must match the `name` of a column declared in the endpoint entity's `extensions.columns` array. Mismatched names emit an EDGE_COLUMN_NOT_FOUND warning.
        - `cardinality` 'one' | 'many' | 'zero-or-one' | 'zero-or-many' | 'one-or-many': ER-style cardinality marker for this endpoint. When not set, inferred from the column the end anchors at each time the diagram is drawn, and not stored.
      - `to` object: Edge target (for kind=edge). elementId may reference any element kind - typically a node, but frame and group IDs are also valid targets. An edge to a frame terminates at the frame's border using cardinal anchors; useful for abstract group dependencies (e.g. an edge to a 'Regional Services' sidebar rather than to a specific service inside). On entityRelationship diagrams, set `column` to anchor the edge at a specific column row inside the entity. Set `cardinality` for an explicit Crow's Foot glyph; when omitted, the engine infers it from the resolved column's pk/fk/unique/nullable metadata.
        - `elementId` string, required
        - `portId` string
        - `anchor` object: Optional explicit anchor point on the element's face. Mutually exclusive with portId.
        - `column` string: Optional column-row anchor for ER entities. Must match the `name` of a column declared in the endpoint entity's `extensions.columns` array. Mismatched names emit an EDGE_COLUMN_NOT_FOUND warning.
        - `cardinality` 'one' | 'many' | 'zero-or-one' | 'zero-or-many' | 'one-or-many': ER-style cardinality marker for this endpoint. When not set, inferred from the column the end anchors at each time the diagram is drawn, and not stored.
      - `router` 'straight' | 'orthogonal' | 'curved' | 'polyline': Edge routing algorithm
      - `children` string[]: Child element IDs (for kind=group or kind=frame)
      - `title` string: A frame's or group's title
      - `containerType` 'generic' | 'pool' | 'lane' | 'subprocess' | 'partition' | 'package' | 'boundary' | 'fragment': Frames: what the frame stands for; generic when absent
      - `laneDirection` 'vertical' | 'horizontal': Pools: how the lanes are arranged; vertical (stacked top to bottom) when absent
      - `style` union: Element style: either a named-style string key (resolved against scene.styles) or an inline StyleValue object. textColor: when omitted, the rendering engine auto-computes a contrast-appropriate color based on the element's resolved fill (light text on dark fills, dark text on light fills). Edge labels, and labels drawn outside their shape (BPMN events and gateways), are checked against what is under them, and are made lighter or darker when they wouldn't read there, even when textColor is set.
        - string: Reference to a named style in scene.styles
        - object: Inline style overrides (fill, stroke, strokeWidth, dash, fontSize, fontFamily, fontWeight, opacity, textColor or its other name color, labelBackground, cornerRadius, accentColor, forceLabel). On ER edges, forceLabel: true draws a label that matches an endpoint column, which is otherwise left out; the edge still anchors at that column.
      - `icon` string: A node's icon: a family:id key such as lucide:database or aws:lambda, or an image URL. The same field as https://zindex.ai/schemas/scene.schema.json describes.
      - `textStyle` object: Text layout for a node's label or a text element: wrap (none, word, character), align, verticalAlign. The same fields as https://zindex.ai/schemas/scene.schema.json describes.
      - `extensions` object: Protocol extensions on an element. ER tables (er.entity, er.weakEntity) take extensions.columns, each column as https://zindex.ai/schemas/scene.schema.json#/$defs/erColumn describes: name, and optionally type, pk, fk, unique, nullable, key and comment.
    - `styles` object: Named style definitions that elements can reference
    - `constraints` object[]: Layout constraints. 10 types: align, distribute, sameSize, stack, centerIn, contain, grid, minGap, noOverlap, order. The order constraint enforces hard spatial relationships when the engine places nodes (e.g. source leftOf target, source above target, sameRank); one added with addConstraint places its two nodes again when they're out of order. The other types apply on every layout. A constraint's id is optional and unique when given.
    - `diagramFamily` 'architecture' | 'workflow' | 'entityRelationship' | 'sequence' | 'network' | 'orgchart' | 'uiflow': Diagram family declaration. Technically optional in the schema but effectively load-bearing: gates family-specific validation, layout, and rendering behaviour. Without it the diagram is drawn as a generic flowchart, and nothing warns you. Always declare on every scene.
    - `layoutStrategy` object: Optional: how the engine places nodes that have no layout. The engine picks its method from diagramFamily; the layered layout (architecture, workflow, entityRelationship, uiflow, and no family) reads these fields, and org charts (top to bottom), network and sequence diagrams don't. Changing it with updateScene places every node again. nodeSpacing and rankSpacing are the spacing the engine aims for, not minimums: laying out every node, it also tries tighter and wider spacings and keeps the layout that scores best.
      - `direction` 'TB' | 'BT' | 'LR' | 'RL': Layout direction
      - `nodeSpacing` number: Pixels the engine aims for between nodes in the same rank. Not a minimum: when it lays out every node it also tries tighter and wider spacings and keeps the layout that scores best, and it widens gaps to fill a larger canvas.
      - `rankSpacing` number: Pixels the engine aims for between adjacent ranks. Not a minimum: when it lays out every node it also tries tighter and wider spacings and keeps the layout that scores best, and it widens gaps to fill a larger canvas.
    - `palette` object: Scene-level color palette that overrides theme defaults for unstyled elements. Keys: background, foreground, nodeFill, nodeStroke, edgeStroke, containerFill, containerStroke, accentPrimary, accentSecondary, mutedForeground. Element explicit styles still win. Note: palette.background does NOT trigger the canvas-aware default-theme auto-selection; only scene.canvas.background does. Set canvas.background if you want a dark canvas to also flip the default theme to dark.
      - `background` string
      - `foreground` string
      - `nodeFill` string
      - `nodeStroke` string
      - `edgeStroke` string
      - `containerFill` string
      - `containerStroke` string
      - `accentPrimary` string: Accent color of nodes whose style sets no accentColor: ER table headers and PK badges, org chart card stripes, UI flow screen and modal title bars.
      - `accentSecondary` string: Nothing draws a second accent yet, so this has no effect.
      - `mutedForeground` string
    - `extensions` object: Reserved extension point for protocol additions. Recognised keys include `showLifelineFeet` (sequence diagrams only) and `autoGroup` (architecture diagrams only).
      - `showLifelineFeet` boolean: Sequence-diagram only. When true (default), the renderer draws a foot bar at the bottom of each lifeline mirroring the head (UML / PlantUML / Mermaid convention). Set to false to suppress foot bars - useful when the author has their own bottom row of shapes.
      - `autoGroup` 'byNodeType': Architecture-family only. When set, the engine synthesises subtle background bands grouping nodes by their `nodeType` (e.g. one band for all `service` nodes, one for `database` nodes, one for `externalSystem` nodes). Each band is labelled with the pluralised type name ("Services", "Databases", "External Systems"). Skipped when the scene already authors frames containing the candidate nodes (no double-wrap), when all nodes share a single `nodeType` (trivial), or when the nodes of a type are scattered (a node of another type sits between two of them in a column). Opt-in only; omit the field to render with no banding (existing default).
  - `key` string: Your stable name for the diagram, e.g. payments-architecture
  - `brief` string: Context for the next agent: what the diagram shows, conventions to keep, decisions made
  - `message` string: One line on why the diagram was made
  - `mermaid` string: Mermaid text, instead of scene: an erDiagram or a flowchart (graph). Converted deterministically: element IDs come from the Mermaid IDs and the same text always gives the same diagram. Subgraphs, edges to them and styles are left out for now and listed in warnings; other diagram types are refused with UNSUPPORTED_DIAGRAM_TYPE, and syntax errors return PARSE_ERROR with line and column.

## Response `200`

A retried create: the existing diagram with the same key and content (created: false)

- DiagramWrite: The write envelope a create and an edit return, kept under 100 tokens.
  - `id` string, required: The diagram ID, e.g. dgm_7Q2kD9a1B2c3
  - `key` string, nullable
  - `revision` integer, required
  - `hash` string, required: sha256: of the RFC 8785 canonical content: the scene as view=dsp and the export return it, without `computed`, the engine's drawing output. The node positions in each node's `layout` are content, so they count.
  - `chain` string, nullable, required: The version's chain hash: sha256: of "zindex-chain/1\n" + the previous kept version's chain hash (empty for the first kept version) + "\n" + this version's content hash + "\n" + its stored image's hash, each as lowercase hex without sha256:. It covers every kept version up to this one. null until Zindex has computed it, normally when the version is saved; a render, an export or the next edit computes a missing one. Reads and the history never compute it. https://zindex.ai/docs/concepts/#verifying
  - `changed` object: The elements the edit added, updated or removed, including nodes a grown node pushed and members a moved frame carried: at most 20 IDs in all, and more counts the rest.
    - `added` string[]
    - `updated` string[]
    - `removed` string[]
    - `more` integer: How many changed IDs were left out of the lists
  - `warnings` DiagramWarning[], required
    - `code` string, required
    - `element` string: The element it concerns, when there is one
    - `fix` string, required: One line on what to change
  - `created` boolean: false when a create with the same key and content returned the existing diagram
  - `noop` boolean: The edit changed nothing; revision is the current one
  - `replayed` boolean: The requestId matched an earlier edit; this is its result

## Other responses

- `201`: Saved
- `400`: Invalid request
- `401`: Missing or invalid API key. Response includes a `code` field that distinguishes the failure mode so clients (dashboard, MCP server) can render actionable copy: `AUTH_REQUIRED` (no key or session was sent), `API_KEY_INVALID` (key not found), `API_KEY_REVOKED` (key was revoked, or its person is no longer in its workspace), `API_KEY_EXPIRED` (key passed its expires_at), and for the site's session cookie `SESSION_EXPIRED` (past its 24 hours), `SESSION_REVOKED` (the account's sessions were ended, for example by a password reset) and `SESSION_INVALID` (not a session this server signed).
- `403`: LIMIT_REACHED, EMAIL_NOT_VERIFIED or READ_ONLY
- `409`: KEY_EXISTS: the key belongs to a diagram with different content, or, in a team workspace, to a teammate's diagram; id and revision name it, so read and edit that one
- `413`: INPUT_TOO_LARGE: Mermaid text over 64 KB
- `422`: VALIDATION_FAILED: the scene has errors, listed in errors. For Mermaid: PARSE_ERROR (with line, column and expected tokens), UNSUPPORTED_DIAGRAM_TYPE, IMPORT_TIMEOUT or IMPORT_FAILED. Nothing was saved. DRAW_TIMEOUT: drawing the scene took longer than the drawing budget (20 s), or needed more memory than it allows; nothing was saved
- `429`: RATE_LIMITED: over the request rate limit (see Rate Limiting). Wait retryAfter seconds (also the Retry-After header), then try again.
- `503`: BUSY: every drawing worker is busy and the queue is full. Wait retryAfter seconds (also the Retry-After header): half the budget of the drawing refused. Then try again.

---

[API](https://skmtc.dev/zindex/apis/zindex-api.md) · [All operations](https://skmtc.dev/zindex/apis/zindex-api/llms.txt) · [OpenAPI document](https://skmtc.dev/zindex/apis/zindex-api/revisions/1d21bd5bbf8e?raw)
