# Plotwork documentation, in full > A small text language and renderer for twenty kinds of diagram, from flow, sequence, and database to Gantt, state, mind map, and treemap, written by AI agents and drawn the same way everywhere: in a README, in your editor, and over MCP. This file holds 227 pages of the Plotwork documentation, one after another, each starting with its own title and the address it is served at. The index of every page, including the optional ones left out here, is https://docs.plotwork.dev/llms.txt. --- # Quick start > Page: https://docs.plotwork.dev/quick-start. Index of every page: https://docs.plotwork.dev/llms.txt Connect an AI agent, ask it for a diagram in plain words, and have a checked picture in about five minutes. It is for anyone who has just added Plotwork to Claude Code, Cursor, VS Code, or Claude Desktop, and for anyone who wants diagrams in a Markdown file instead. ## TL;DR | Question | Answer | |---|---| | What is Plotwork? | A small text language for twenty kinds of diagram (flow, sequence, database schema, git graph, Gantt chart, class, state, timeline, user journey, mind map, treemap, pie chart, quadrant chart, layered architecture, C4, matrix, Sankey, XY chart, tree, and swimlane), and a renderer. Your agent writes the text, Plotwork checks it and draws it. You never draw boxes by hand. | | What do I do first? | [Connect your agent](https://docs.plotwork.dev/mcp/connect.md) once, then paste the first prompt in step 2. | | What do I say to the agent? | Plain words: what the diagram shows and who it is for. You do not name tools. Copy a prompt from this page or from the [examples](https://docs.plotwork.dev/examples.md). | | What comes back? | The diagram text, any mistakes with line and hint, a plain-text outline to check the meaning, and the picture in light and dark. | | How do I see the picture? | Ask the agent to save it as a file, put it in a Markdown file, or, on the hosted server, publish a link. Step 4 has the words to use. | | How do I change it? | Say what is wrong, such as "put a cache between the API and the database". The agent edits the text and draws again. | | No agent? | Write a `diagram` fence in a Markdown file and send the file to the HTTP API, which answers with the picture files to save. This needs the Pro plan and an API key. See step 8. | ## 1. Connect your agent Do this once per client. The quickest way is the local server: it runs on your machine, needs Node 22 or newer, and needs no key and no account. For Claude Code: ```sh claude mcp add plotwork -- npx -y @buun_group/plotwork-mcp@0.1.0-beta.10 ``` Then run `/mcp` inside Claude Code. `plotwork` should be listed as connected. For Cursor, VS Code, Claude Desktop, the hosted server, and sign-in without a key, follow [Connecting an agent](https://docs.plotwork.dev/mcp/connect.md). Check it worked by asking your agent: "Which Plotwork tools do you have?" It should name `draw_diagram`, `get_diagram_syntax`, `draw_from_pattern`, and others. If it names none, see [Troubleshooting](https://docs.plotwork.dev/mcp/connect.md#6-troubleshooting). ## 2. Ask for your first diagram Paste this into your agent: ```text Use Plotwork to draw a diagram of how a login request is checked. A user sends an email and password to an Auth API. The API looks the user up in a Users database. If the password is correct a session is issued, otherwise the API answers 401 Unauthorized. Draw it, then tell me in one sentence what it shows. ``` Here is what happens, step by step. You see these as tool calls in your client. | Step | Tool | What it does | |---|---|---| | 1 | `get_diagram_syntax` | The agent reads the language once. | | 2 | `draw_diagram` | The agent sends the diagram text. If the text has mistakes, Plotwork returns every one with its line, column, and a hint, and draws nothing. | | 3 | `draw_diagram` again | The agent fixes each mistake and calls again until the text is valid. A valid diagram comes back with warnings (such as a suggested icon), an outline, and the picture. | | 4 | none | The agent compares the outline with your request and answers. | You did not write any diagram text. The agent did. ## 3. Read what the agent wrote This is the kind of text the agent sends. It is plain, one statement per line, and you can read it and edit it by hand. **Diagram source** ```diagram flow "How a login request is checked" desc="A user signs in through an API that checks the password against a database and answers with a session or an error." direction=down node user person "User" node api service "Auth API" sub="POST /login" node db database "Users DB" node check decision "Password correct?" node ok terminal "Session issued" icon=status:success node deny terminal "401 Unauthorized" icon=status:error user -> api : "email and password" api -> db : "look up user" role=data db -> check check -> ok : "yes" check -> deny : "no" ``` *Picture: How a login request is checked: 6 nodes, 5 connections. A user signs in through an API that checks the password against a database and answers with a session or an error.* **Text description** **Nodes:** - User, person - Auth API (POST /login), service - Users DB, database - Password correct?, decision - Session issued, terminal - 401 Unauthorized, terminal **Connections:** - User to Auth API: email and password - Auth API to Users DB (stored data line): look up user - Users DB to Password correct? - Password correct? to Session issued: yes - Password correct? to 401 Unauthorized: no | Line | Meaning | |---|---| | `flow "Title" direction=down` | The first line names the kind (`flow`, `sequence`, or `schema`) and gives a title. `direction` says which way it reads. | | `node id type "Label"` | One box. The id is how arrows refer to it. The type (`person`, `service`, `database`, `decision`, `terminal`, and more) picks the icon and the shape. | | `a -> b : "label"` | An arrow from `a` to `b`, with an optional label. `-->` draws a dashed arrow. | | `group id "Name" { ... }` | A box around other nodes, for a network, a tier, or a team. See the [examples](https://docs.plotwork.dev/examples.md). | The full language, kind by kind, is in [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md). ## 4. See the picture The agent gets the picture as SVG text. Your client shows it as text unless the agent saves or publishes it, so ask for what you want: | You want | Say | |---|---| | A file to open | "Save the light and dark SVGs to docs/diagrams/login.svg and docs/diagrams/login.dark.svg." Open the file in a browser or in VS Code. | | The diagram in a README or doc | "Add this diagram to README.md under a Login heading, and render it." The agent uses `sync_markdown`, and GitHub shows the picture and switches to dark mode on its own. See [Markdown and GitHub](https://docs.plotwork.dev/markdown-and-github.md). | | A link to paste in chat or a pull request | "Publish it and give me the link." This needs the [hosted server](https://docs.plotwork.dev/hosted-diagrams.md); it returns a public link and a Markdown image to paste. | | A private copy for your team | "Publish it to the Platform organisation." Members of the [organisation](https://docs.plotwork.dev/organisations.md) see it after signing in. Nobody else can. | A large diagram, about 30 nodes or more, comes back without its picture so that the answer fits what a client can read. The check and the outline still arrive. Ask for a file or a link and you get the picture. ## 5. Change it Say what is wrong in words. The agent edits the text and draws again. These all work: ```text Add a Redis cache between the API and the database, and show that the API reads from it first. ``` ```text Rename "Auth API" to "Identity service", and put the service and the database in a group called "Backend". ``` ```text Use AWS icons: API Gateway for the entry point, Lambda for the service, and DynamoDB for the database. ``` ```text Make it read left to right, and add a step after "Session issued" that sends a welcome email. ``` ```text Number the steps. ``` ```text Animate the flow. ``` You can also edit the text yourself and tell the agent: "I changed the diagram, check it and draw it again." ## 6. Start from something that exists You do not need a blank page. These prompts start from a checked pattern or from Mermaid you already have: ```text Draw a three-tier network on Azure with a load balancer in front, and call the app tier "Billing API". ``` ```text Convert this Mermaid flowchart to Plotwork, tell me what was lost, and draw it. ``` ```text Replace the Mermaid diagrams in README.md with Plotwork diagrams and give me the files to write. ``` More prompts, each with the tool it leads to, are in [Prompts and recipes](https://docs.plotwork.dev/agents/prompts.md). Ready-made diagrams you can copy are in the [examples](https://docs.plotwork.dev/examples.md). ## 7. If something is not right | Symptom | Cause | Fix | |---|---|---| | The agent writes a Mermaid diagram or ASCII art | It did not call the tools | Say "Use Plotwork" in the prompt, or add the [rules file](https://docs.plotwork.dev/agents/rules.md) to your project. | | The agent keeps fixing the same mistake | The hint is not being applied | Ask it to read the error out loud, then to read the language again with `get_diagram_syntax`. | | No picture in the answer | The diagram is large, or the client hides tool output | Ask for a file or a link, as in step 4. | | The picture is too wide to read | Many boxes in one row | Say "make it read top to bottom" or "split it into two diagrams". | | The icons are generic | The agent did not look up product names | Say "choose icons for every service with `map_icons`". | ## 8. Without an agent: Markdown and the HTTP API The text is the same without an agent. Write a `diagram` fence in any Markdown file, using text like the example in step 3: ````md ```diagram flow "Login request" node user person "User" node api service "Auth API" node db database "Users DB" user -> api : "POST /login" api -> db : "look up user" role=data ``` ```` The [command line](https://docs.plotwork.dev/cli/quick-start.md) draws every fence of a file on your machine, with no account or key: ```sh npx -y @buun_group/plotwork@0.1.0-beta.13 sync notes.md ``` Over HTTP, Plotwork draws the fences of a document in one call, `POST /v1/markdown/sync`, the HTTP form of the `sync_markdown` tool. Calling the drawing routes of the API directly needs the [Pro plan](https://docs.plotwork.dev/plans-and-billing.md) and an API key from [app.plotwork.dev](https://app.plotwork.dev). Send the whole file and its path. The command reads the key from the environment variable `PLOTWORK_API_KEY`: ```sh jq -n --rawfile markdown notes.md '{markdown: $markdown, documentPath: "notes.md"}' | curl -s -X POST https://api.plotwork.dev/v1/markdown/sync \ -H "X-API-Key: $PLOTWORK_API_KEY" \ -H "content-type: application/json" \ -d @- > result.json ``` `result.json` holds the rewritten document in `markdown`, the light and dark SVG files in `files`, each with its `path` and `content`, and a `report` with the line and a hint for every mistake. Plotwork writes nothing itself: save `markdown` over `notes.md` and each entry of `files` at its path. Send `"mode": "check"` as well and the call changes nothing and says in `changed` whether a picture is stale, which is the form to run in CI. How the picture block works and where it shows is in [Markdown and GitHub](https://docs.plotwork.dev/markdown-and-github.md). ## Next steps | Doc | Why | |---|---| | [Examples to copy](https://docs.plotwork.dev/examples.md) | Eight ready diagrams with the prompt for each: a login flow, services, a sequence, a schema, AWS, a release pipeline, numbered steps, and a data flow that moves | | [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md) | Flow, sequence, and schema diagrams, groups, icons, and cloud architecture, with a guide for each of the seven other kinds | | [Prompts and recipes](https://docs.plotwork.dev/agents/prompts.md) | What to say and which tool answers | | [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md) | Publish a link to a diagram | | [Organisations](https://docs.plotwork.dev/organisations.md) | Share a private diagram library with your team | ## See also | Doc | Why | |---|---| | [Connecting an agent](https://docs.plotwork.dev/mcp/connect.md) | Client settings, sign-in, and troubleshooting | | [MCP server](https://docs.plotwork.dev/mcp/overview.md) | The tools and how they behave | | [Language reference](https://docs.plotwork.dev/reference/language.md) | Every word the language accepts | | [How a diagram is drawn](https://docs.plotwork.dev/how-it-works.md) | What happens between your text and the picture | --- # Examples to copy > Page: https://docs.plotwork.dev/examples. Index of every page: https://docs.plotwork.dev/llms.txt Eight finished diagrams with the text that makes each one and the prompt that gets an agent to write it. Copy the text into a `diagram` fence, or paste the prompt into your agent and change the names to yours. It is for anyone who learns faster from a working diagram than from a reference. ## TL;DR | Question | Answer | |---|---| | How do I use one? | Two ways. Paste the prompt into your agent and change the names, or copy the text into a `diagram` fence in a Markdown file and ask your agent to render the file. | | Which kinds are here? | Flow (a login check, an online shop, a serverless app on AWS, a release pipeline, and a data flow that moves), sequence (a sign-in, and a password reset with numbered steps), and schema (a shop database). | | What about the other kinds? | Git graphs, Gantt charts, class diagrams, state diagrams, timelines, user journeys, and mind maps each have a finished example in their own guide: see [Git graphs](https://docs.plotwork.dev/git-graphs.md), [Gantt charts](https://docs.plotwork.dev/gantt-charts.md), [Class diagrams](https://docs.plotwork.dev/class-diagrams.md), [State diagrams](https://docs.plotwork.dev/state-diagrams.md), [Timelines](https://docs.plotwork.dev/timelines.md), [User journeys](https://docs.plotwork.dev/user-journeys.md), and [Mind maps](https://docs.plotwork.dev/mind-maps.md). | | Can I change them? | Yes. Rename nodes, add lines, change `direction`. Ask your agent to check the text: it names the line and says how to fix it. | | Where is the full language? | [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md), kind by kind, and the [language reference](https://docs.plotwork.dev/reference/language.md). | | Where are cloud examples? | [Cloud examples](https://docs.plotwork.dev/cloud-architecture/examples.md) for Azure, AWS, and Google Cloud, and [Architecture patterns](https://docs.plotwork.dev/architecture-patterns.md) for ready-made networks. | ## Starter text Start from one of these when none of the examples is close. Replace the names and add lines. ```plotwork flow "Title" direction=down node a service "First" node b database "Second" a -> b : "label" ``` ```plotwork sequence "Title" actor a client "First" actor b service "Second" a -> b : "Request" b --> a : "Response" ``` ```plotwork schema "Title" table things { id uuid pk name text } table parts { id uuid pk thing_id uuid fk } parts.thing_id -> things.id ``` ## 1. A login check A flow with a decision. Two possible endings, drawn as terminal nodes with status icons. Ask your agent: ```text Use Plotwork to draw how a login request is checked. A user sends an email and password to an Auth API, which looks the user up in a Users database. If the password is correct a session is issued, otherwise the API answers 401 Unauthorized. ``` **Diagram source** ```diagram flow "How a login request is checked" desc="A user signs in through an API that checks the password against a database and answers with a session or an error." direction=down node user person "User" node api service "Auth API" sub="POST /login" node db database "Users DB" node check decision "Password correct?" node ok terminal "Session issued" icon=status:success node deny terminal "401 Unauthorized" icon=status:error user -> api : "email and password" api -> db : "look up user" role=data db -> check check -> ok : "yes" check -> deny : "no" ``` *Picture: How a login request is checked: 6 nodes, 5 connections. A user signs in through an API that checks the password against a database and answers with a session or an error.* **Text description** **Nodes:** - User, person - Auth API (POST /login), service - Users DB, database - Password correct?, decision - Session issued, terminal - 401 Unauthorized, terminal **Connections:** - User to Auth API: email and password - Auth API to Users DB (stored data line): look up user - Users DB to Password correct? - Password correct? to Session issued: yes - Password correct? to 401 Unauthorized: no ## 2. An online shop with groups Groups draw a box around related nodes. `type=edge` and `type=data-tier` give a group its icon and its color. Ask your agent: ```text Use Plotwork to draw an online shop. A web app calls an API gateway. Behind it are Orders, Payments and Inventory services, and each has its own database. Orders calls Payments to charge and Inventory to reserve items. Put the gateway in an Edge group, the services in a Services group, and the databases in a Databases group. ``` **Diagram source** ```diagram flow "Online shop services" desc="A web app calls an API gateway. The orders service asks the payments and inventory services to do their part, and each service keeps its own database." direction=down node client client "Web app" sub="Browser" group edge "Edge" type=edge { node gw gateway "API gateway" sub="TLS, rate limits" } group services "Services" { node orders service "Orders" node payments service "Payments" node stock service "Inventory" } group data "Databases" type=data-tier { node ordersdb database "Orders DB" node paydb database "Payments DB" node stockdb database "Inventory DB" } client -> gw gw -> orders : "POST /orders" orders -> payments : "charge" orders -> stock : "reserve items" orders -> ordersdb payments -> paydb stock -> stockdb ``` *Picture: Online shop services: 8 nodes, 7 connections. A web app calls an API gateway. The orders service asks the payments and inventory services to do their part, and each service keeps its own database.* **Text description** **Nodes:** - Web app (Browser), client - Edge: Edge - API gateway (TLS, rate limits), gateway - Group: Services - Orders, service - Payments, service - Inventory, service - Data tier: Databases - Orders DB, database - Payments DB, database - Inventory DB, database **Connections:** - Web app to API gateway - API gateway to Orders: POST /orders - Orders to Payments: charge - Orders to Inventory: reserve items - Orders to Orders DB - Payments to Payments DB - Inventory to Inventory DB ## 3. A sign-in sequence A sequence diagram reads top to bottom, one line per message. `-->` is a reply. An `alt` block shows two outcomes. Ask your agent: ```text Use Plotwork to draw a sequence diagram of signing in with an identity provider. The user clicks Sign in in the web app, the app redirects to the identity provider, the user enters credentials, the provider returns an authorization code, and the app exchanges it for an access token. Then the app calls the Orders API. If the token is expired the API answers 401 and the app refreshes it. ``` **Diagram source** ```diagram sequence "Sign in with an identity provider" desc="A web app sends the user to an identity provider, receives a code, and trades it for tokens." actor user person "User" actor app client "Web app" actor idp service "Identity provider" actor api service "Orders API" user -> app : "Click Sign in" app -> idp : "Redirect with client id" idp -> user : "Ask for credentials" user -> idp : "Credentials" idp --> app : "Authorization code" app -> idp : "Exchange code for tokens" idp --> app : "Access token" alt "token is valid" { app -> api : "GET /orders with token" api --> app : "200 orders" } else "token expired" { api --> app : "401" app -> idp : "Refresh token" } ``` *Picture: Sign in with an identity provider: 4 participants. A web app sends the user to an identity provider, receives a code, and trades it for tokens.* **Text description** **Participants:** - User, person - Web app, client - Identity provider, service - Orders API, service **Steps:** - 1. User to Web app: Click Sign in - 2. Web app to Identity provider: Redirect with client id - 3. Identity provider to User: Ask for credentials - 4. User to Identity provider: Credentials - 5. Identity provider to Web app (dashed): Authorization code - 6. Web app to Identity provider: Exchange code for tokens - 7. Identity provider to Web app (dashed): Access token - alt: token is valid - 8. Web app to Orders API: GET /orders with token - 9. Orders API to Web app (dashed): 200 orders - else: token expired - 10. Orders API to Web app (dashed): 401 - 11. Web app to Identity provider: Refresh token ## 4. A shop database A schema diagram draws tables with their columns. `pk` marks a primary key, `fk` a foreign key, and a line below the tables joins one column to another. Ask your agent: ```text Use Plotwork to draw the database of an online shop: customers (id, email, created_at), products (id, name, price in cents), orders (id, customer, placed_at, status), and order_items (id, order, product, quantity). Orders belong to customers, and each order item points at an order and a product. ``` **Diagram source** ```diagram schema "Online shop" desc="Customers place orders. An order has many items, and each item points at a product." table customers sub="People who buy" { id uuid pk email text unique created_at timestamptz } table products { id uuid pk name text price int note="In cents" } table orders { id uuid pk customer_id uuid fk placed_at timestamptz status text } table order_items { id uuid pk order_id uuid fk product_id uuid fk quantity int } orders.customer_id -> customers.id order_items.order_id -> orders.id order_items.product_id -> products.id ``` *Picture: Online shop: 4 tables, 3 relations. Customers place orders. An order has many items, and each item points at a product.* **Text description** **Tables:** - customers - id uuid (primary key) - email text (unique) - created_at timestamptz - products - id uuid (primary key) - name text - price int: In cents - orders - id uuid (primary key) - customer_id uuid (foreign key) - placed_at timestamptz - status text - order_items - id uuid (primary key) - order_id uuid (foreign key) - product_id uuid (foreign key) - quantity int **Relations:** - orders.customer_id references customers.id (many to one) - order_items.order_id references orders.id (many to one) - order_items.product_id references products.id (many to one) ## 5. A serverless app on AWS Cloud diagrams add `icon=aws:...` to a node, and `type=account` and `type=region` to groups. Ask your agent to choose the icons: it calls `map_icons` for every product name and never guesses. Ask your agent: ```text Use Plotwork to draw a serverless image resizer on AWS. An upload to an S3 bucket triggers a Lambda function that resizes the image, writes it to a second S3 bucket, and sends logs to CloudWatch. Put it all in an AWS account and the eu-west-1 region. ``` **Diagram source** ```diagram flow "Serverless image resizer on AWS" desc="An upload to S3 triggers a Lambda function that resizes the image, writes it to a second bucket, and logs to CloudWatch." direction=down provider=aws node user client "Uploader" group aws "AWS account" type=account { group region "eu-west-1" type=region { node uploads storage "Uploads" icon=aws:s3 node resize function "Resize image" icon=aws:lambda sub="Node.js" node resized storage "Resized images" icon=aws:s3 node logs service "Logs" icon=aws:cloudwatch } } user -> uploads : "PUT image" uploads --> resize : "object created" resize -> resized : "write thumbnail" role=data resize --> logs : "metrics and logs" ``` *Picture: Serverless image resizer on AWS: 5 nodes, 4 connections. An upload to S3 triggers a Lambda function that resizes the image, writes it to a second bucket, and logs to CloudWatch.* **Text description** **Nodes:** - Uploader, client - Account: AWS account - Region: eu-west-1 - Uploads, storage - Resize image (Node.js), function - Resized images, storage - Logs, service **Connections:** - Uploader to Uploads: PUT image - Uploads to Resize image (dashed): object created - Resize image to Resized images (stored data line): write thumbnail - Resize image to Logs (dashed): metrics and logs For Azure and Google Cloud, replace `aws:` with `azure:` or `gcp:` and the product name. [Cloud examples](https://docs.plotwork.dev/cloud-architecture/examples.md) has a complete diagram for each, and [Cloud architecture](https://docs.plotwork.dev/cloud-architecture.md) explains accounts, regions, networks, and subnets. ## 6. A release pipeline Status icons (`status:success`, `status:warning`, `status:error`) show how each step ended, which suits a pipeline, a runbook, or an incident timeline. Ask your agent: ```text Use Plotwork to draw a release pipeline. A merged pull request is built and tested. A passing build is deployed to staging. Someone approves it, and it goes to production. If they do not approve, the release is held. Show each step with a success icon, and the held end with a warning. ``` **Diagram source** ```diagram flow "Release pipeline" desc="A merged pull request is built and tested. A passing build is deployed to staging, then to production after approval." direction=down node pr step "Merged pull request" node build step "Build" icon=status:success node test step "Test" icon=status:success node staging step "Deploy to staging" icon=status:success node approve decision "Approved?" node prod terminal "Production" icon=status:success node hold terminal "Held" icon=status:warning pr -> build build -> test test -> staging staging -> approve approve -> prod : "yes" approve -> hold : "no" ``` *Picture: Release pipeline: 7 nodes, 6 connections. A merged pull request is built and tested. A passing build is deployed to staging, then to production after approval.* **Text description** **Nodes:** - Merged pull request, step - Build, step - Test, step - Deploy to staging, step - Approved?, decision - Production, terminal - Held, terminal **Connections:** - Merged pull request to Build - Build to Test - Test to Deploy to staging - Deploy to staging to Approved? - Approved? to Production: yes - Approved? to Held: no ## 7. Numbered steps in a sequence Add `numbered=on` to the header and every message gets a numbered badge, so people can say "step 4" aloud. Write `step=off` on a message to leave its badge out. Ask your agent: ```text Use Plotwork to draw how a password reset works as a sequence with numbered steps. A user asks the web app for a reset, the app asks the Auth API, the API has the email service send a link, the user opens the link and chooses a new password, and the API confirms. ``` **Diagram source** ```diagram sequence "Password reset" numbered=on desc="A user asks for a reset link, receives it by email, and chooses a new password. Each message carries its step number." actor user person "User" actor app client "Web app" actor api service "Auth API" actor mail service "Email" user -> app : "Forgot password" app -> api : "Request reset" api -> mail : "Send reset link" mail --> user : "Email arrives" user -> app : "Choose new password" app -> api : "Set password" api --> app : "Password changed" ``` *Picture: Password reset: 4 participants. A user asks for a reset link, receives it by email, and chooses a new password. Each message carries its step number.* **Text description** **Participants:** - User, person - Web app, client - Auth API, service - Email, service **Steps:** - 1. User to Web app: Forgot password - 2. Web app to Auth API: Request reset - 3. Auth API to Email: Send reset link - 4. Email to User (dashed): Email arrives - 5. User to Web app: Choose new password - 6. Web app to Auth API: Set password - 7. Auth API to Web app (dashed): Password changed ## 8. A data flow that moves Add `animate=on` to the header and every line with an arrowhead gets dashes that travel toward it. Write `animate=off` on a line to keep it still. The motion shows when a browser or GitHub shows the SVG as an image, and it is off for readers who ask for reduced motion. Ask your agent: ```text Use Plotwork to draw how page views flow from a website to a warehouse: a Collector, an Event stream, and a Processor in between. Animate the lines, except the acknowledgement the Processor sends back to the Collector. ``` **Diagram source** ```diagram flow "Clickstream into analytics" animate=on direction=down desc="Page views travel from a website through a collector and a stream to a processor, which loads them into a warehouse. The acknowledgement is drawn still." node site client "Website" node collector gateway "Collector" node stream queue "Event stream" node processor function "Processor" node warehouse database "Warehouse" site -> collector : "page view" collector -> stream : "append" stream -> processor : "read" role=data processor -> warehouse : "load" processor --> collector : "ack" animate=off ``` *Picture: Clickstream into analytics: 5 nodes, 5 connections. Page views travel from a website through a collector and a stream to a processor, which loads them into a warehouse. The acknowledgement is drawn still.* **Text description** **Nodes:** - Website, client - Collector, gateway - Event stream, queue - Processor, function - Warehouse, database **Connections:** - Website to Collector: page view - Collector to Event stream: append - Event stream to Processor (stored data line): read - Processor to Warehouse: load - Processor to Collector (dashed): ack ## Share what you made | You want | Do | |---|---| | A picture file | Ask your agent: "Save this diagram as SVG files for light and dark." | | The diagram in a README | Put the text in a `diagram` fence and ask your agent: "Render the diagrams in README.md." See [Markdown and GitHub](https://docs.plotwork.dev/markdown-and-github.md). | | A link | On the hosted server, ask: "Publish this and give me the link." See [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md). | | A private copy for your team | Ask: "Publish this to the Platform organisation." See [Organisations](https://docs.plotwork.dev/organisations.md). | ## See also | Doc | Why | |---|---| | [Quick start](https://docs.plotwork.dev/quick-start.md) | Connect an agent and draw a first diagram | | [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md) | The language, kind by kind | | [Prompts and recipes](https://docs.plotwork.dev/agents/prompts.md) | More requests and the tool each leads to | | [Cloud examples](https://docs.plotwork.dev/cloud-architecture/examples.md) | Complete Azure, AWS, and Google Cloud diagrams | | [Architecture patterns](https://docs.plotwork.dev/architecture-patterns.md) | Checked, ready-made diagrams to start from | --- # Writing diagrams > Page: https://docs.plotwork.dev/writing-diagrams. Index of every page: https://docs.plotwork.dev/llms.txt How to write flow, sequence, and schema diagrams in the diagram language, with a pointer to the guide of each of the seventeen other kinds. There are twenty kinds in all. It is for anyone, human or AI agent, who writes diagram source. Every example on this page is a real diagram, rendered by the tool this guide teaches, so you can copy one and it works. ## TL;DR | Rule | Yes | No | |---|---|---| | Statements | One per line | Two statements on one line | | Labels | Quoted: `"Auth API"` | Bare words with spaces | | Meaning | A node `type` (`database`) or a `tone=` (`violet`) | A color value such as `#7c3aed`; colors are not accepted | | Ids | Short and stable: `api`, `user_db`, `as-jwks` | Spaces, or an id reused for two things | | Icons | `icon=lucide:key`, `icon=simple:postgresql`, `icon=aws:lambda`, `icon=status:success` | A made-up name; it is rejected with suggestions | | Groups | A name and a type for what the group is: `group vpc "Application VPC" type=network {` | A color, or a long sentence as the name | | Size | About 15 nodes, 6 tables, or 6 actors at most | One diagram that tries to show everything | | Labels | Short, in sentence case | A paragraph inside a node | | Step numbers | `numbered=on` in the header, `step=off` on a line to skip one | A number typed into each label | ## 1. How a diagram starts The first line names the kind, gives a quoted title, and may add options. The title becomes the accessible name of the picture. | Kind | Header | Options | |---|---|---| | Flow | `flow "Title"` | `direction=` (`auto`, `right`, `down`, `left`, `up`), `desc="Summary"`, `animate=on`, `numbered=on`, `start=` | | Sequence | `sequence "Title"` | `desc="Summary"`, `animate=on`, `numbered=on`, `start=` | | Schema | `schema "Title"` | `desc="Summary"` | | Git graph | `git "Title"` | `direction=`, `main=`, `desc=`. See [Git graphs](https://docs.plotwork.dev/git-graphs.md). | | Gantt chart | `gantt "Title"` | `start=`, `unit=`, `today=`, `weekends=`, `holidays=`, `desc=`. See [Gantt charts](https://docs.plotwork.dev/gantt-charts.md). | | Class diagram | `class "Title"` | `direction=`, `desc=`. See [Class diagrams](https://docs.plotwork.dev/class-diagrams.md). | | State diagram | `state "Title"` | `direction=`, `desc=`. See [State diagrams](https://docs.plotwork.dev/state-diagrams.md). | | Timeline | `timeline "Title"` | `direction=`, `side=`, `desc=`. See [Timelines](https://docs.plotwork.dev/timelines.md). | | User journey | `journey "Title"` | `marks=`, `legend=`, `desc=`. See [User journeys](https://docs.plotwork.dev/user-journeys.md). | | Mind map | `mindmap "Title"` | `layout=`, `desc=`. See [Mind maps](https://docs.plotwork.dev/mind-maps.md). | `desc` is a one- or two-sentence summary. It is used as the picture's alt text, so write what the diagram says. `animate`, `numbered`, and `start` are explained in [Numbered steps](#7-numbered-steps) and [Animated lines](#8-animated-lines). Lines that start with `#` are comments. ## 2. Flow diagrams A flow diagram shows things and the connections between them. Use it for architecture and for processes. | Statement | Form | Notes | |---|---|---| | Node | `node "Label" sub="Detail" tone=blue icon=lucide:key stack=simple:go` | `sub`, `tone`, `icon`, and `stack` are optional. `stack=` adds up to three technology marks under the text. | | Group | `group "Name" type=network provider=aws {`, then nodes, then `}` | `type`, `provider`, `icon`, and `tone` are optional. Groups nest. `type=` says what the group is and sets its icon, tone, and frame style. | | Edge | `a -> b : "label" icon=status:yes role=data animate=off step=12` | The label, the icon, `role=`, `animate=`, and `step=` are optional. An icon needs a label. A label that names a relationship with a verb, such as `reads` or `publishes OrderPlaced`, draws its icon by itself. | Arrows: | Token | Meaning | |---|---| | `->` | Solid line, arrowhead at the end | | `-->` | Dashed line, arrowhead at the end | | `..>` | Dotted line, arrowhead at the end: an asynchronous call such as a webhook or an event | | `<->` and `<-->` | Arrowheads at both ends | | `--` | A line with no arrowhead | Where one line crosses another at a right angle, the horizontal one hops over the other with a small arc, so a reader sees a crossing and not a join, in solid and dashed lines alike. Lines that meet or share a stretch are not crossings and do not hop. A bend right beside a hop tightens a little to make room, and a hop is left out where it would still land on a bend, an arrowhead, a node, or a label. There is nothing to write for it. A node's type sets its default icon, tone, and look. The [flow node types](https://docs.plotwork.dev/reference/language/flow-nodes.md) page lists every type. These are the most used: | Type | Use for | |---|---| | `person`, `agent`, `client` | Who or what starts things | | `service`, `gateway`, `function` | Code that runs | | `database`, `cache`, `queue`, `storage` | Where data lives | | `identity`, `secret` | Trust and credentials | | `external` | Systems you do not own (dashed outline) | | `step`, `decision`, `terminal` | Process steps, branches, and end states | A group draws a frame around its nodes and carries a corner tab: a small box fixed to the frame's top-left corner that holds the group's icon and its name on one line. The tab grows to fit the name, with the same padding on both sides. A name that is longer than the widest node card is cut with an ellipsis and warning `W203`, so keep a group's name to a few words and put detail in the nodes. | Group option | Effect | |---|---| | `type=` | What the group is, from a closed list such as `account`, `region`, `network`, `subnet`, `data-tier`, or `trust-boundary`. It sets the icon, the tone, and a solid, dashed, or dotted frame. | | `provider=` | Whose resources the group holds (`aws`, `azure`, `gcp`), so a type draws that provider's icon. Nested groups inherit it. | | `icon=:` | Overrides the icon in the tab. A group with no type has none unless you set one. | | `tone=` | Overrides the tone. Without a type the group is neutral. | | `sub="..."` | A short second line in the tab under the name, such as an address range. | | `stack=:,...` | Up to three small marks in the tab, written like the marks of a node. | | `icon=none` | Removes the icon. | | `type=none` | Keeps the group plain: no type from its name and no suggestion. | A group with no `type=` whose name gives its type for certain takes it by itself: `VPC`, `Production`, `CI pipeline`, `Kafka`, `Kubernetes`, `us-east-1`, `Data`, `Security`, and `Monitoring` are typed, while `Data science team` and `Data pipeline` stay plain. A name that only suggests a type gets warning `W205` with the `type=` to add. `icons=off` on the header turns this off along with the automatic icons. An overlay draws a dashed frame around nodes or groups it does not own, wherever layout put them. It is one line after the members, `overlay set "Web machines" type=scale-group members=w1,w2,w3`, and it takes `type=`, `provider=`, `icon=`, `tone=`, `sub=`, and `stack=` like a group. Use it for a scaling group that spans two zones or a redundant set of machines inside a tier. When the members sit in different groups, the frame crosses the group frames on purpose, and warning `W206` says if it would also surround a node that is not a member. A group nested in another is shaded one step stronger than the one around it, in light and in dark. The [group types page](https://docs.plotwork.dev/cloud-architecture/group-types.md) draws every group type and the icon each draws for each provider, the [cloud architecture guide](https://docs.plotwork.dev/cloud-architecture.md) has the recipe and the icon pages for every cloud product, and the [architecture patterns guide](https://docs.plotwork.dev/architecture-patterns.md) has ready-made diagrams for the common shapes. An edge that would pass under a tab is moved to the side when the nodes it joins allow it, and otherwise passes behind the tab. **Diagram source** ```diagram flow "Nested groups and directions" desc="A request crosses a trust boundary into a private network containing an API and its data stores." direction=down node client person "Client" group edge "Public edge" type=edge { node gw gateway "API gateway" sub="TLS and rate limits" } group internal "Private network" type=trust-boundary tone=violet { node api service "Orders API" group data "Data stores" icon=lucide:database { node db database "Orders DB" node cache cache "Session cache" } } client -> gw : "HTTPS" gw -> api : "forward" api -> db : "read and write" role=data api --> cache : "lookup" ``` *Picture: Nested groups and directions: 5 nodes, 4 connections. A request crosses a trust boundary into a private network containing an API and its data stores.* **Text description** **Nodes:** - Client, person - Edge: Public edge - API gateway (TLS and rate limits), gateway - Trust boundary: Private network - Orders API, service - Data tier: Data stores - Orders DB, database - Session cache, cache **Connections:** - Client to API gateway: HTTPS - API gateway to Orders API: forward - Orders API to Orders DB (stored data line): read and write - Orders API to Session cache (dashed): lookup ### Make it look finished Four habits make a flow diagram look finished without a single colour value: | Habit | Write | What it draws | |---|---|---| | A verb on each arrow | `api -> db : "reads orders"` | The relationship's icon in the label: an eye for reads, a pen for writes, a paper plane for publishes, a rocket for deploys | | A role on the lines that mean something | `role=data`, `role=async`, `role=identity`, `role=private` | The line in the role's colour, named in a legend. Warning `W500` lists the arrows to mark | | A type on each group | `type=network`, or a name such as `"Production"` | The frame, tone, and icon of the kind | | Product names on nodes | `"Postgres"`, `"Redis"`, `"Amazon S3"` | The product's mark | `W204`, `W205`, and `W500` are style suggestions: `validate --strict` and the Action's `fail-on: warning` pass them. **Diagram source** ```diagram flow "Checkout" direction=right desc="A web app calls the checkout API, which writes orders to Postgres, reads sessions from Redis, and publishes an event that an email worker consumes." node web client "Web app" node api service "Checkout API" group data "Data" { node db database "Postgres" node cache cache "Redis" } group events "Event bus" { node bus queue "Kafka" } node mailer service "Email worker" web -> api : "calls" api -> db : "writes orders" role=data api -> cache : "reads sessions" role=data api -> bus : "publishes OrderPlaced" role=async bus -> mailer : "consumes" role=async ``` *Picture: Checkout: 6 nodes, 5 connections. A web app calls the checkout API, which writes orders to Postgres, reads sessions from Redis, and publishes an event that an email worker consumes.* **Text description** **Nodes:** - Web app, client - Checkout API, service - Email worker, service - Data tier: Data - Postgres, database - Redis, cache - Messaging: Event bus - Kafka, queue **Connections:** - Web app to Checkout API: calls - Checkout API to Postgres (stored data line): writes orders - Checkout API to Redis (stored data line): reads sessions - Checkout API to Kafka (asynchronous line): publishes OrderPlaced - Kafka to Email worker (asynchronous line): consumes ## 3. Sequence diagrams A sequence diagram shows messages between actors over time. Use it for protocols, login flows, and code paths. | Statement | Form | Notes | |---|---|---| | Actor | `actor "Label" sub="Detail" icon=lucide:key tone=blue` | Declared in column order, left to right. | | Message | `a -> b : "label" animate=off step=off` | Use `-->` for a reply. `a -> a` is a message to itself. `animate=` and `step=` are optional. | | Note | `note over a : "text"` or `note over a, b : "text"` | The second form spans several actors. | | Frame | `alt "condition" {`, messages, `} else "other" {`, messages, `}` | Also `opt`, `loop`, and `par` (with `and` for the next branch). | A message between two actors that are not neighbours crosses the lifelines of the actors between them. Each crossing is drawn as a small arc that the arrow hops over, so a reader sees a crossing and not a join, in solid and dashed arrows alike. There is nothing to write for it. **Diagram source** ```diagram sequence "Checkout with retries" desc="A client pays for an order. The payment is retried up to three times, and inventory is updated in parallel with the receipt." actor app client "Shop app" actor pay service "Payments" actor inv service "Inventory" actor mail service "Email" app -> pay : "Charge order" loop "up to 3 attempts" { pay -> pay : "Call card network" pay --> app : "Result" } note over app, pay : "A declined card stops the flow after the third attempt" opt "payment accepted" { par "update stock" { pay -> inv : "Reserve items" } and "send receipt" { pay -> mail : "Receipt" } } ``` *Picture: Checkout with retries: 4 participants. A client pays for an order. The payment is retried up to three times, and inventory is updated in parallel with the receipt.* **Text description** **Participants:** - Shop app, client - Payments, service - Inventory, service - Email, service **Steps:** - 1. Shop app to Payments: Charge order - loop: up to 3 attempts - 2. Payments to Payments: Call card network - 3. Payments to Shop app (dashed): Result - Note over Shop app and Payments: A declined card stops the flow after the third attempt - opt: payment accepted - par: update stock - 4. Payments to Inventory: Reserve items - and: send receipt - 5. Payments to Email: Receipt ## 4. Schema diagrams A schema diagram shows database tables, their columns, and the relations between them. | Statement | Form | Notes | |---|---|---| | Table | `table sub="Detail" icon=lucide:table tone=aqua {`, then columns, then `}` | `sub`, `icon`, and `tone` are optional. | | Column | ` pk fk unique nullable note="Why"` | Flags and `note` are optional and may be combined. | | Relation | `child.column -> parent.column : "label"` | Written from the referencing column to the referenced one. The label is optional; leaving it out keeps wide schemas narrower. | A relation draws a crow's foot at the many end. It draws a single tick at the one end, and at both ends when the referencing column is `unique`. **Diagram source** ```diagram schema "Orders" desc="Orders belong to customers; each order has one invoice and many items." table customers sub="People who buy" { id uuid pk email text unique } table orders { id uuid pk customer_id uuid fk placed_at timestamptz note text nullable } table invoices { id uuid pk order_id uuid fk unique note="One invoice per order" } orders.customer_id -> customers.id invoices.order_id -> orders.id ``` *Picture: Orders: 3 tables, 2 relations. Orders belong to customers; each order has one invoice and many items.* **Text description** **Tables:** - customers - id uuid (primary key) - email text (unique) - orders - id uuid (primary key) - customer_id uuid (foreign key) - placed_at timestamptz - note text (nullable) - invoices - id uuid (primary key) - order_id uuid (foreign key, unique): One invoice per order **Relations:** - orders.customer_id references customers.id (many to one) - invoices.order_id references orders.id (one to one) ## 5. Icons Every node, actor, table, and group can carry one icon, set with `icon=:`. A group shows its icon in its corner tab. | Set | Contents | Example | |---|---|---| | `lucide` | General icons | `icon=lucide:key-round` | | `simple` | Technology and brand logos | `icon=simple:postgresql` | | `status` | Colored outcome icons | `icon=status:success` | | `aws`, `gcp`, `azure`, `alibaba`, `oci`, `ibm`, `cloudflare`, `digitalocean`, `vercel` | A provider and its products | `icon=aws:lambda`, `icon=gcp:bigquery`, `icon=azure:functions`, `icon=alibaba:oss` | Find a name with the `search_icons` tool, or ask your agent to choose icons for you. Search matches ideas as well as names: `token exchange`, `validate`, `revoke`, `timeout`, and `bug` each find suitable icons. It also knows products: `lambda`, `s3`, `gke`, `cosmos db`, `bigquery`, and `serverless function` find the right provider icon first. The [icon reference](https://docs.plotwork.dev/reference/icons.md) shows every icon. A name that does not exist is an error with a suggestion, so a typo never renders as a blank space, and a guessed provider such as `amazon:s3` is pointed at `aws:s3`. Status icons keep their own color on any node: green for good, red for bad, amber for caution, blue for information, and neutral for waiting. The full list, with meanings, is in the [language reference](https://docs.plotwork.dev/reference/language/icon-sets.md#status-icons). **Diagram source** ```diagram flow "Deployment pipeline" desc="A change is built, tested, and deployed. A failed test stops the pipeline." direction=down node build step "Build" icon=status:success node test step "Test" icon=status:error sub="2 failures" node deploy step "Deploy" icon=status:skipped node notify step "Notify team" icon=status:warning build -> test test -> deploy : "blocked" test --> notify : "failed" ``` *Picture: Deployment pipeline: 4 nodes, 3 connections. A change is built, tested, and deployed. A failed test stops the pipeline.* **Text description** **Nodes:** - Build, step - Test (2 failures), step - Deploy, step - Notify team, step **Connections:** - Build to Test - Test to Deploy: blocked - Test to Notify team (dashed): failed ### Cloud architecture Name a product with its provider and its own name: `aws:lambda`, `gcp:pubsub`, `azure:entra-id`. Give the group that holds a cloud's resources `type=account` and its `provider=`, which draws the provider's own icon (`:cloud`), and nest a `type=region` group for each region. The [cloud architecture guide](https://docs.plotwork.dev/cloud-architecture.md) covers the whole recipe, and the icon pages ([AWS](https://docs.plotwork.dev/cloud-icons/aws.md), [Azure](https://docs.plotwork.dev/cloud-icons/azure.md), [Google Cloud](https://docs.plotwork.dev/cloud-icons/google-cloud.md)) draw every product. Where a provider publishes an official icon for a product, or has a licensed brand mark, the icon draws it; every other product draws the glyph for its kind of service, so a function looks like a function whichever cloud it runs on. The label says whose it is. **Diagram source** ```diagram flow "Multi-cloud order platform" desc="Orders enter through AWS, are stored and queued in one region, replicated to Google Cloud for analytics, while customers sign in through Azure." direction=down node users client "Customers" sub="Web and mobile" group aws "AWS" type=account provider=aws tone=orange { node cdn gateway "CloudFront" icon=aws:cloudfront group eu "eu-west-1" type=region { node api gateway "API Gateway" icon=aws:api-gateway node fn function "Order handler" icon=aws:lambda sub="Validates and stores" node db database "Orders table" icon=aws:dynamodb node queue queue "Order events" icon=aws:sqs } } group gcp "Google Cloud" type=account provider=gcp tone=blue { node feed queue "Event feed" icon=gcp:pubsub node analytics database "Analytics" icon=gcp:bigquery } group azure "Azure" type=account provider=azure tone=violet { node sso identity "Sign-in" icon=azure:entra-id } users -> cdn : "HTTPS" cdn -> api api -> fn fn -> db fn -> queue queue -> feed : "replicate" feed -> analytics users -> sso : "SSO" ``` *Picture: Multi-cloud order platform: 9 nodes, 8 connections. Orders enter through AWS, are stored and queued in one region, replicated to Google Cloud for analytics, while customers sign in through Azure.* **Text description** **Nodes:** - Customers (Web and mobile), client - Account: AWS - CloudFront, gateway - Region: eu-west-1 - API Gateway, gateway - Order handler (Validates and stores), function - Orders table, database - Order events, queue - Account: Google Cloud - Event feed, queue - Analytics, database - Account: Azure - Sign-in, identity **Connections:** - Customers to CloudFront: HTTPS - CloudFront to API Gateway - API Gateway to Order handler - Order handler to Orders table - Order handler to Order events - Order events to Event feed: replicate - Event feed to Analytics - Customers to Sign-in: SSO ## 6. Decisions and outcomes A `decision` node is a tinted card with a branch icon. Label the outgoing edges with the answers. A label that is exactly `yes` or `true` gets a green tick, and `no` or `false` gets a red cross, without any extra syntax. Add `icon=` to an edge for another icon, or `icon=none` to remove the automatic one. A `terminal` node is a pill, and it can carry a status icon for the outcome. **Diagram source** ```diagram flow "Review outcome" desc="A pull request is approved or sent back for changes." direction=down node pr step "Pull request opened" node review decision "Does the change pass review?" node merge terminal "Merged" icon=status:success node changes terminal "Changes asked" icon=status:retry pr -> review review -> merge : "yes" review -> changes : "no" ``` *Picture: Review outcome: 4 nodes, 3 connections. A pull request is approved or sent back for changes.* **Text description** **Nodes:** - Pull request opened, step - Does the change pass review?, decision - Merged, terminal - Changes asked, terminal **Connections:** - Pull request opened to Does the change pass review? - Does the change pass review? to Merged: yes - Does the change pass review? to Changes asked: no ## 7. Numbered steps Add `numbered=on` to the header of a flow or a sequence diagram and every line gets a numbered badge, counting in the order the lines are written. In a sequence diagram the badge sits on the message, and the messages inside `alt`, `opt`, `loop`, and `par` blocks are numbered in the order they are read. In a flow diagram it sits on the connection. Use it for a procedure, a protocol, or a runbook, where a reader says "step 4" aloud. | Where | Option | What it does | |---|---|---| | Header | `numbered=on` | Numbers every line, starting at 1. | | Header | `start=5` | Starts at 5 instead. A whole number from 0 to 999, and it needs `numbered=on`. | | One line | `step=off` | Draws no badge on this line, and uses up no number. | | One line | `step=12` | Gives this line the number 12. The lines after it continue from 13. | A `step=` on a line of a diagram that is not numbered is an error, and so is a count that runs past 999. A sequence diagram: **Diagram source** ```diagram sequence "Sign in with an emailed code" numbered=on desc="A user asks for a code, receives it by email, and trades it for a session. Each message carries its step number." actor user person "User" actor web client "Web app" actor api service "Auth API" actor mail service "Email" user -> web : "Enter email" web -> api : "Request code" api -> mail : "Send the code" mail --> user : "Email arrives" user -> web : "Type the code" web -> api : "Verify code" api --> web : "Session cookie" ``` *Picture: Sign in with an emailed code: 4 participants. A user asks for a code, receives it by email, and trades it for a session. Each message carries its step number.* **Text description** **Participants:** - User, person - Web app, client - Auth API, service - Email, service **Steps:** - 1. User to Web app: Enter email - 2. Web app to Auth API: Request code - 3. Auth API to Email: Send the code - 4. Email to User (dashed): Email arrives - 5. User to Web app: Type the code - 6. Web app to Auth API: Verify code - 7. Auth API to Web app (dashed): Session cookie A flow diagram, where the number sits on each connection: **Diagram source** ```diagram flow "Release steps" numbered=on direction=down desc="A change is pushed, built, published as an image, and deployed. Each connection carries its step number." node dev person "Developer" node git service "Git host" node ci service "CI" node reg storage "Registry" node prod service "Production" dev -> git : "Push" git -> ci : "Trigger build" ci -> reg : "Publish image" icon=lucide:upload ci -> prod : "Deploy" ``` *Picture: Release steps: 5 nodes, 4 connections. A change is pushed, built, published as an image, and deployed. Each connection carries its step number.* **Text description** **Nodes:** - Developer, person - Git host, service - CI, service - Registry, storage - Production, service **Connections:** - 1. Developer to Git host: Push - 2. Git host to CI: Trigger build - 3. CI to Registry: Publish image - 4. CI to Production: Deploy ## 8. Animated lines Add `animate=on` to the header of a flow or a sequence diagram and every line that has an arrowhead gets moving dashes that travel toward the head. It shows which way data or control goes, which suits a pipeline or a request path. To animate one line only, write `animate=on` on that line. To keep one line still while the header animates the rest, write `animate=off` on it. | Rule | What happens | |---|---| | `animate=on` in the header | Every line with an arrowhead moves. A `--` line has no arrowhead, so it stays still. | | `animate=off` on a line | That line stays still, whatever the header says. | | `animate=on` on a `--` line | An error, because dashes travel toward an arrowhead and the line has none. Use `->`, or remove the option. | | A line with two arrowheads (`<->`) | It moves from its first end to its second. | The motion is part of the SVG, so it moves wherever a browser shows the SVG as an image, including a README on GitHub and this page. It stays still in a PNG and in a reader that flattens the SVG into a picture, and it is switched off when the reader asks for reduced motion in their system settings. The picture means the same thing without it, so never rely on motion to say something that the text does not. **Diagram source** ```diagram flow "Events into the warehouse" animate=on direction=down desc="Events flow from an app through an API and a queue to a processor, which stores them. The acknowledgement is drawn still." node app client "Mobile app" node api gateway "Events API" node queue queue "Event queue" node worker function "Processor" node db database "Warehouse" app -> api : "POST /events" api -> queue : "enqueue" role=async queue -> worker : "consume" role=async worker -> db : "store" role=data worker --> api : "ack" animate=off ``` *Picture: Events into the warehouse: 5 nodes, 5 connections. Events flow from an app through an API and a queue to a processor, which stores them. The acknowledgement is drawn still.* **Text description** **Nodes:** - Mobile app, client - Events API, gateway - Event queue, queue - Processor, function - Warehouse, database **Connections:** - Mobile app to Events API: POST /events - Events API to Event queue (asynchronous line): enqueue - Event queue to Processor (asynchronous line): consume - Processor to Warehouse (stored data line): store - Processor to Events API (dashed): ack ## 9. Fitting text Text never overflows its box, and padding stays the same on every box. | Situation | What happens | |---|---| | A label is longer than a card | It wraps onto more lines, up to three, and the card grows taller. | | One word is longer than a card | The card widens to hold it, up to 400px. | | Text is still too long | It is cut with an ellipsis and the diagram gets warning `W203`. The full text stays in the text description. | | A group name is long | The corner tab grows to fit it on one line, up to the width of the widest card (400px). Past that the name is cut with an ellipsis and warning `W203`. | | A message label is long in a sequence diagram | The columns spread apart to make room, and `W202` appears if the diagram then becomes too wide. | The fix for a warning is almost always shorter text. Move detail into `sub=`, a note, or the prose around the diagram. ## 10. Quality warnings Warnings never stop rendering. They point at things a reader will notice. | Code | Meaning | Fix | |---|---|---| | `W200` | Too many nodes, tables, or actors | Split along a group boundary or by domain. | | `W201` | A node is not connected to anything | Connect it or remove it. | | `W202` | The diagram is wider than a Markdown column and its text would shrink | Use `direction=down`, shorten labels, or split it. | | `W203` | Text was cut with an ellipsis, in a node, an actor, or a group name | Shorten the text. | | `W204` | A node has no `icon=`, and its label names one product | Add the `icon=` the warning gives, or write `icon=none`. | | `W205` | A group has no `type=`, and its name matches one group type | Add the `type=` the warning gives. | ## See also | Doc | Why | |---|---| | [Cloud architecture](https://docs.plotwork.dev/cloud-architecture.md) | Typed groups, provider icons, and how to map a product name to an icon | | [Architecture patterns](https://docs.plotwork.dev/architecture-patterns.md) | Ready-made, checked diagrams to start from | | [Language reference](https://docs.plotwork.dev/reference/language.md) | Every type, tone, icon, arrow, and diagnostic code | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The validate and fix loop | | [Diagrams in Markdown and on GitHub](https://docs.plotwork.dev/markdown-and-github.md) | Putting diagrams in a document | | [How a diagram is drawn](https://docs.plotwork.dev/how-it-works.md) | What happens between your text and the picture | --- # Diagrams in Markdown and on GitHub > Page: https://docs.plotwork.dev/markdown-and-github. Index of every page: https://docs.plotwork.dev/llms.txt How a `diagram` fence in a Markdown file becomes a picture that GitHub displays, and how to keep it current. It is for anyone adding diagrams to a README or a doc. ## TL;DR | Rule | Yes | No | |---|---|---| | Source | Write a `diagram` fence and have your agent sync the file with `sync_markdown` | Paste SVG by hand | | Pictures | Commit the generated `.svg` and `.dark.svg` files | Leave them out of git; the page would show broken images | | Freshness | Run `npx @buun_group/plotwork sync --check` in CI; see [Keep diagrams in sync in CI](https://docs.plotwork.dev/diagrams-in-ci.md) | Trust that nobody forgot to sync | | Edits | Change the fence, then sync | Edit the generated block between the markers | | Paths | Keep the pictures in `assets/diagrams` next to the document, or choose a folder with `assetsDir` | Move the files without syncing again | ## 1. What GitHub does and does not render GitHub draws Mermaid fences itself, in the page. It does not draw custom fences, and it will not run a renderer for them. Plotwork therefore renders before commit: the picture is a file in the repository, and the Markdown refers to it. **Diagram source** ```diagram flow "From fence to picture" desc="An author writes a diagram fence, sync renders it to SVG files and a picture block, and GitHub shows the picture." node author person "Author or agent" sub="Writes a diagram fence" node sync function "sync_markdown" sub="Parses, lays out, renders" node svgs storage "SVG files" sub="Light and dark, committed" node block step "Picture block" sub="Inserted under the fence" node github terminal "GitHub page" icon=status:success author -> sync : "Markdown file" sync -> svgs : "writes" role=data sync -> block : "writes" role=data svgs -> github : "loaded by" block -> github : "references" ``` *Picture: From fence to picture: 5 nodes, 5 connections. An author writes a diagram fence, sync renders it to SVG files and a picture block, and GitHub shows the picture.* **Text description** **Nodes:** - Author or agent (Writes a diagram fence), person - sync_markdown (Parses, lays out, renders), function - SVG files (Light and dark, committed), storage - Picture block (Inserted under the fence), step - GitHub page, terminal **Connections:** - Author or agent to sync_markdown: Markdown file - sync_markdown to SVG files (stored data line): writes - sync_markdown to Picture block (stored data line): writes - SVG files to GitHub page: loaded by - Picture block to GitHub page: references ## 2. What a sync produces For each top-level `diagram` fence, a sync makes four changes. `sync_markdown` reads and writes no file itself: it returns the rewritten document and the SVG files, and the caller, usually your agent, saves them. | Change | Where | Purpose | |---|---|---| | A collapsed "Diagram source" section | Around the fence | Readers see the picture first and can open the source. | | A picture block | Directly under the section | An HTML `picture` element: the light SVG by default, the dark SVG when the reader's color scheme is dark. | | A collapsed "Text description" | Inside the picture block | The diagram as plain text, so it can be read without the image and by tools that skip images. | | Two SVG files | `assets/diagrams/.svg` and `.dark.svg` next to the document | The pictures. The id comes from the diagram title. | The picture block starts and ends with marker comments and carries a hash. The hash covers the source and the rendered output, so a change to the diagram or to the renderer makes the block stale and the next sync rewrites it. Do not edit between the markers. A fence that is indented, for example inside a list item, is still rendered but is not wrapped in the collapsed section. A picture written by a sync carries its diagram source in its metadata, so `read_diagram` can give the source back from the picture alone. See [Editing an existing diagram](https://docs.plotwork.dev/editing-existing-diagrams.md). ## 3. Running a sync Ask your agent: "Render the diagrams in README.md". The agent sends the whole file and its path to `sync_markdown`, then writes the `markdown` it gets back over the file and each entry of `files` at its `path`. The local server does this with no key and no account. The same operation is `POST /v1/markdown/sync` over HTTP, which needs an API key and the Pro plan; the [quick start](https://docs.plotwork.dev/quick-start.md#8-without-an-agent-markdown-and-the-http-api) has a request you can run. | Input | What it does | |---|---| | `markdown` and `documentPath` | The whole document as it is on disk, and where it lives, relative to the repository root with forward slashes. Picture links and file paths are worked out from the path. | | `mode` | `sync`, the default, returns the rewritten document and the files to write. `check` changes nothing and reports whether a picture is stale. | | `assetsDir` | The folder for the SVG files, relative to the repository root. The default is `assets/diagrams` next to the document. | | `prefix` | Starts every picture file name with this text. Use it when several documents share one asset folder. | | `convertMermaid` | When true, every Mermaid fence that can be converted becomes a `diagram` fence first. See [Migrating from Mermaid](https://docs.plotwork.dev/migrating-from-mermaid.md). | A diagram with an error is a normal result, not a failure: its fence is left as it was, the report names its line and column inside the document with a hint, and the other fences still sync. The limits on the size of a document are in the [tool reference](https://docs.plotwork.dev/mcp/tools/sync-markdown.md). ## 4. Where the pictures show up | Place | Expected | Note | |---|---|---| | GitHub README and Markdown files | Yes | GitHub documents the `picture` element with a color-scheme source for light and dark images. | | Editor preview (VS Code and similar) | Yes | Relative image paths load in the preview. | | npm package page | Uncertain | npm may refuse to show SVG images in a README. | | MCP (Model Context Protocol) clients | No | A client receives text and cannot show an SVG. Use `outline_diagram` for the text form. | | GitLab and other hosts | Probably | They need to allow SVG in an image tag. | ## 5. Check your page after you push | Check | What to look for | |---|---| | Light mode | The light picture loads and the text is crisp. | | Dark mode | Switching GitHub to a dark theme loads the dark picture, with readable text and visible lines. | | Phone width | The picture scales down to the screen and does not scroll sideways. | | Collapsed sections | "Diagram source" and "Text description" open and show their content. | | Nested folders | A picture in a subfolder loads; its path goes up to the shared assets folder, for example `../assets/diagrams`. | | Alt text | Hovering or using a screen reader gives the diagram's summary. | Most failures come from a path that points outside the repository or from a picture file that was not committed. ## 6. Troubleshooting | Symptom | Cause | Fix | |---|---|---| | The page shows a broken image | The SVG is not committed, or the path moved | Sync the file again, then commit the SVG files next to the document. | | A check with `mode` set to `check` says a block is stale | The fence or the renderer changed | Sync the file again and commit the result. | | An SVG is left over after a diagram was renamed or removed | A sync returns pictures and never deletes old ones | Delete the unused files. | | The fence shows as plain code on GitHub | The file was never synced | Sync it, write the result, and commit. | | Two documents show each other's picture | Same title in a shared asset folder | Give each document its own `prefix`. | ## See also | Doc | Why | |---|---| | [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md) | The language by example | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The validate and fix loop | | [How a diagram is drawn](https://docs.plotwork.dev/how-it-works.md) | How source becomes SVG | --- # Keep diagrams in sync in CI > Page: https://docs.plotwork.dev/diagrams-in-ci. Index of every page: https://docs.plotwork.dev/llms.txt How to fail a pull request whose diagram pictures no longer match their source, with the GitHub Action or the command line. It is for anyone who keeps Plotwork diagrams in Markdown or `.diagram` files in a repository. ## TL;DR | Question | Answer | |---|---| | What is checked? | Every `diagram` fence in the files you name: a fence with no picture under it, a picture whose source or renderer changed, and source with mistakes. `.diagram` files are checked for mistakes. | | What runs the check on GitHub? | The action `plotwork-official/plotwork-action`, pinned by commit. The engine is bundled into it, so it installs nothing and needs no account, key, or token. | | How does a failure show in a pull request? | Each stale fence and each mistake is an error annotation on its line in the Files changed tab, and the job summary lists the files with the command that fixes them. | | How do I fix a failure? | Run the action with `command: sync` on the same files and commit the Markdown and the SVGs it writes, or run `npx -y @buun_group/plotwork@0.1.0-beta.13 sync` on your machine. | | What else can the action do? | `sync` redraws in the workspace and commits nothing, `validate` reports mistakes only, `render` draws `.diagram` files to SVG, and `generate` makes diagrams from Terraform, OpenAPI, Compose and other files and fails or opens a pull request when they drift (section 5). | | What about other CI? | The command line runs the same check: `npx -y @buun_group/plotwork@0.1.0-beta.13 sync --check "docs/**/*.md"`. See [CI usage](https://docs.plotwork.dev/cli/ci.md). | | Does the check send my diagrams anywhere? | No. The action and the command line count nothing and, by default, make no request. Either can read an organisation's saved style rules when you name the organisation, and `generate` in write mode talks to GitHub to push and open a pull request, and to Plotwork only with `publish: true`. Both read your [repository settings](https://docs.plotwork.dev/repository-settings.md) file on the machine they run on. | ## 1. Check every pull request Copy this workflow to `.github/workflows/plotwork.yml`: ```yaml name: plotwork on: pull_request: paths: ['**/*.md', '**/*.diagram'] permissions: contents: read jobs: diagrams: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: persist-credentials: false - uses: plotwork-official/plotwork-action@dcec4036897b73bd3ea724b3991f4fd0ac880757 # v1.0.0-beta.14 with: paths: | README.md docs/**/*.md ``` The samples pin the latest release of the Action by its commit. `@v1` becomes available at the first stable release. With no `paths`, the action reads every Markdown file in the repository. A line in `paths` that starts with `!` leaves out what it matches, such as `!docs/drafts/**`. Pin `actions/checkout` to a full commit SHA too, as you would any third-party action. A stale diagram looks like this in the log, and as an error on line 12 of the file in the pull request: ```text ::error file=docs/architecture.md,line=12,title=Stale diagram::The picture under this diagram is missing or out of date.%0AFix: run plotwork-official/plotwork-action@v1.0.0-beta.14 with command: sync, then commit the Markdown file and the SVG files. ``` The job summary gives the same fix: run the action with `command: sync` and the same `paths`, then commit what it wrote. Once the command line is on npm, the summary and the annotation also give an `npx` command that pins the version built from the same engine as the action, so the pictures it draws pass the check. ## 2. The commands | `command` | Reads | Writes | Fails when | |---|---|---|---| | `check` (default) | `paths` and `diagrams` | Nothing | A picture is missing or stale, or a diagram has an error | | `sync` | `paths` | The Markdown files and their SVGs, in the workspace | A diagram has an error | | `validate` | `paths` and `diagrams` | Nothing | A diagram has an error, or a warning with `fail-on: warning` | | `render` | `diagrams` | A light and a dark SVG per `.diagram` file, in `out-dir` | A diagram has an error | | `generate` | The `sources` of the settings file | Nothing with `mode: check`; with `mode: write`, the diagrams, then a pull request or a commit | A diagram drifted from its files (check mode), or a diagram has an error | ### Redraw and open a pull request `sync` redraws the stale pictures in the runner's workspace and commits nothing; it never holds a token. To commit from a workflow, give the write permissions to the job and add a step that commits, such as `peter-evans/create-pull-request`: ```yaml name: plotwork-sync on: push: branches: [main] paths: ['**/*.md'] permissions: contents: read jobs: sync: runs-on: ubuntu-latest permissions: contents: write pull-requests: write steps: - uses: actions/checkout@v7 with: persist-credentials: false - id: plotwork uses: plotwork-official/plotwork-action@dcec4036897b73bd3ea724b3991f4fd0ac880757 # v1.0.0-beta.14 with: command: sync paths: docs/**/*.md - if: steps.plotwork.outputs.stale != '0' uses: peter-evans/create-pull-request@v8 with: branch: plotwork/sync commit-message: 'docs: redraw stale diagrams' title: 'docs: redraw stale diagrams' add-paths: docs/** ``` The repository must allow GitHub Actions to create pull requests (Settings, Actions, General). Most teams keep `check` on pull requests and run the sync locally. ### Report mistakes only ```yaml - uses: plotwork-official/plotwork-action@dcec4036897b73bd3ea724b3991f4fd0ac880757 # v1.0.0-beta.14 with: command: validate diagrams: diagrams/**/*.diagram fail-on: warning ``` ### Draw `.diagram` files ```yaml - uses: plotwork-official/plotwork-action@dcec4036897b73bd3ea724b3991f4fd0ac880757 # v1.0.0-beta.14 with: command: render diagrams: diagrams/**/*.diagram out-dir: build/diagrams ``` `diagrams/flow.diagram` becomes `build/diagrams/diagrams/flow.svg` and `flow.dark.svg`. Each SVG carries its source, so [Editing an existing diagram](https://docs.plotwork.dev/editing-existing-diagrams.md) works on it. ## 3. Inputs and outputs | Input | Default | Meaning | |---|---|---| | `command` | `check` | `check`, `sync`, `validate`, `render`, or `generate`. | | `paths` | `**/*.md` | Markdown files: one glob, file, or folder per line. `node_modules` and `.git` are never searched. | | `diagrams` | `**/*.diagram` | `.diagram` files, in the same form. Set it to `''` for none. | | `out-dir` | `plotwork-svg` | Where `render` writes, inside the repository. | | `fail-on` | `error` | `error`, `warning`, or `never`. Problems are annotated either way. | | `convert-mermaid` | `false` | When `true`, `check` and `sync` treat each `mermaid` fence that converts as a diagram. See [Migrating from Mermaid](https://docs.plotwork.dev/migrating-from-mermaid.md). | | `config` | empty | The [repository settings](https://docs.plotwork.dev/repository-settings.md): header defaults every command applies, and rules `check` and `validate` apply. Empty looks for `plotwork.config.json`, then `.plotwork/config.json`, from `working-directory` up to the repository root; `off` reads none. | | `rules-org` | empty | An organisation whose saved style rules to apply instead of the file's rules, read with the key in `PLOTWORK_API_KEY`. | | `mode`, `on-drift`, `pr-branch`, `pr-title`, `commit-message`, `publish`, `github-token` | | `generate` only. See [Diagrams as code](https://docs.plotwork.dev/diagrams-as-code.md), section 7. | | `summary` | `true` | Write the job summary. | | `working-directory` | `.` | The folder to run in. Annotations still name files from the repository root. | | Output | Meaning | |---|---| | `stale` | Diagrams whose picture was missing or out of date. | | `errors`, `warnings` | The problems found. | | `files` | A JSON array: the files that need a sync (`check`), the files written (`sync`, `render`), or the files with an error (`validate`). | | `pull-request`, `published` | `generate` only: the pull request it opened or edited, and what it published. | `check`, `validate`, and `render` need only `contents: read`. `generate` in check mode does too; in write mode it needs `contents: write`, and `pull-requests: write` to open a pull request. ## 4. The command line The npm package `@buun_group/plotwork` holds the bin `plotwork`, built from the same engine as the action. It runs the same check on any CI, as one command with an exit code: ```sh npx -y @buun_group/plotwork@0.1.0-beta.13 sync --check "docs/**/*.md" ``` Pin the version, so a new release cannot change your build without a change you review. The [CLI tab](https://docs.plotwork.dev/cli/overview.md) has every command, with the pipeline for [GitLab CI, Azure Pipelines, Jenkins, and CircleCI](https://docs.plotwork.dev/cli/ci.md), the [exit codes and output formats](https://docs.plotwork.dev/cli/exit-codes-and-output.md), and the [command reference](https://docs.plotwork.dev/cli/commands.md). ## 5. Diagrams made from files When a diagram is made from files of the repository, such as a Terraform stack or an OpenAPI document, name it in `sources` in the settings file, and let `generate` keep it in step. The full guide is [Diagrams as code](https://docs.plotwork.dev/diagrams-as-code.md). ### A pull request check ```yaml name: diagrams on: pull_request: permissions: contents: read jobs: drift: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: persist-credentials: false - uses: plotwork-official/plotwork-action@dcec4036897b73bd3ea724b3991f4fd0ac880757 # v1.0.0-beta.14 with: command: generate ``` ### Regenerate on main and open a pull request ```yaml name: diagrams-regenerate on: push: branches: [main] schedule: - cron: '0 4 * * *' permissions: contents: read jobs: regenerate: runs-on: ubuntu-latest permissions: contents: write pull-requests: write steps: - uses: actions/checkout@v7 - uses: plotwork-official/plotwork-action@dcec4036897b73bd3ea724b3991f4fd0ac880757 # v1.0.0-beta.14 with: command: generate mode: write ``` The repository must allow GitHub Actions to create pull requests (Settings, Actions, General). A pull request opened with the workflow's own token starts no workflow; pass a GitHub App or personal token in `github-token` to have its checks run. ### Publish to an organisation ```yaml name: diagrams-publish on: push: branches: [main] permissions: contents: read jobs: publish: runs-on: ubuntu-latest permissions: contents: write pull-requests: write steps: - uses: actions/checkout@v7 - uses: plotwork-official/plotwork-action@dcec4036897b73bd3ea724b3991f4fd0ac880757 # v1.0.0-beta.14 with: command: generate mode: write publish: true env: PLOTWORK_API_KEY: ${{ secrets.PLOTWORK_API_KEY }} ``` Each source with a `publish` section, such as `{ "organization": "acme", "folder": "Platform" }`, is published with the organisation key in the secret. Only the generated diagram source is sent, never the files it was made from. ## See also | Page | Why | |---|---| | [Markdown and GitHub](https://docs.plotwork.dev/markdown-and-github.md) | What a sync writes and why the pictures are committed | | [Migrating from Mermaid](https://docs.plotwork.dev/migrating-from-mermaid.md) | Convert existing Mermaid fences first | | [Repository settings](https://docs.plotwork.dev/repository-settings.md) | Defaults for every header, and rules a pull request must pass | | [Diagrams as code](https://docs.plotwork.dev/diagrams-as-code.md) | Diagrams made from Terraform, OpenAPI, Compose and other files, kept in step | | [Slack, email, Confluence, and Notion](https://docs.plotwork.dev/wikis-and-chat.md) | Share a picture outside the repository | | [Local usage sharing](https://docs.plotwork.dev/mcp/usage.md) | What the MCP package can count, which the command line and the action never do | --- # Diagrams as code > Page: https://docs.plotwork.dev/diagrams-as-code. Index of every page: https://docs.plotwork.dev/llms.txt Make diagrams from the files they describe (Terraform, OpenAPI, Docker Compose, Kubernetes, Helm, Kustomize, the AWS CDK, Backstage catalogs, Structurizr workspaces, AsyncAPI documents, Prisma, Drizzle, SQL, GitHub Actions, GitLab CI/CD, and Azure Pipelines pipelines, CSV and JSON, folder listings, and git logs) and keep them in step: a pull request whose diagrams no longer match fails, and a workflow writes them again. Hand changes live in an overrides file, so they survive every run. It is for anyone who keeps architecture or schema diagrams in a repository. A working repository to copy from is [plotwork-official/plotwork-examples](https://github.com/plotwork-official/plotwork-examples). ## TL;DR | Question | Answer | |---|---| | Where do I say what to draw? | In `sources` in [`plotwork.config.json`](https://docs.plotwork.dev/repository-settings.md): for each diagram, the files (`from`), their `format`, and where it goes (`out`), a `.diagram` file or a fence of a Markdown file. | | What makes the diagrams? | `npx @buun_group/plotwork generate` on your machine, or the GitHub Action with `command: generate`. Both run the same importers as the `import_source` tool, locally. | | How do I change a generated diagram? | In its overrides file (`infra.overrides.json` beside `infra.diagram`): labels, icons, tones, types, groups, hidden boxes, extra edges and lines, header options. Never in the generated file, which the next run writes over. | | I already edited the file by hand. | Run `plotwork generate --capture`: it writes your edits into the overrides file, then makes the diagram again. | | What fails a pull request? | A diagram that differs from what its files make now. The check names what changed: nodes and edges added, removed, and changed. | | How are the diagrams written back? | The Action in write mode opens or edits one pull request (the default), or commits to the branch with `on-drift: commit`. | | Can it publish too? | Yes, with `publish: true` and an API key in a secret: a stable link for a personal key, or the organisation library for an organisation key. | | Is there a whole example? | [plotwork-examples](https://github.com/plotwork-official/plotwork-examples): a small company's Terraform, OpenAPI, Compose, Kubernetes, SQL, workflow, and CSV files, with a source for each, an overrides file, a Markdown fence, and the check, regenerate, and smoke workflows. | | Will the pictures look right on GitHub in dark mode? | Set `"schemes": "auto"`: each diagram gets one SVG that follows the reader's light or dark setting, so a plain link or `` shows it right. | | Does anything leave the machine? | Not by default. Only publishing sends anything, and then only the generated diagram source, never the files it was made from. | ## 1. Name the sources Each entry of `sources` is one diagram. Paths start at the folder that holds the settings file. ```json { "$schema": "https://docs.plotwork.dev/schemas/plotwork.config.json", "sources": [ { "name": "infra", "from": "infra/**/*.tf", "format": "terraform", "out": "docs/infra.diagram" }, { "name": "api", "from": "openapi.yaml", "format": "openapi", "view": "map", "out": "docs/api.md#endpoints" }, { "name": "services", "from": [ "compose.yaml", "compose.override.yaml" ], "format": "compose", "out": "docs/architecture.md#services" }, { "name": "schema", "from": "prisma/schema.prisma", "format": "prisma", "out": "docs/schema.diagram", "options": { "tables": [ "User", "Org*" ] } } ] } ``` | Field | Meaning | |---|---| | `from` | A file, a folder, or a glob, or a list of them. One that starts with `!` leaves out what it matches. `.git`, `node_modules`, and `.terraform` are never read. | | `format` | `sql`, `prisma`, `drizzle`, `openapi`, `terraform`, `terraform-plan`, `bicep`, `arm`, `pulumi`, `cloudformation`, `cdk`, `compose`, `kubernetes`, `helm`, `kustomize`, `backstage`, `structurizr`, `asyncapi`, `github-actions`, `gitlab-ci`, `azure-pipelines`, `csv`, `json`, `tree`, or `git-log`, as [Importing from code and data](https://docs.plotwork.dev/importing-from-code-and-data.md) describes. | | `out` | A `.diagram` file, or a Markdown file and a fence id: `docs/architecture.md#services`. | | `name` | A short name for `--source` and the summary. The `out` path when left out. | | `view`, `title` | The picture of a format drawn more than one way (`endpoints`, `map`, `sequence` for OpenAPI; `git`, `timeline` for a git log; `context`, `container`, `component` for a Structurizr workspace), and the title. | | `options` | The importer's options: `tables`, `paths`, `tags`, `operationId`, `chart`, `groupBy` (`owner` for a Backstage catalog), `varFiles`, and `root`, the root module or CDK stack of several to draw (`envs/prod`), each a diagram of its own when left out, or the system or container a Structurizr view is about. | | `overrides` | The overrides file. Left out: `infra.diagram` has `infra.overrides.json`, and the fence `services` of `docs/architecture.md` has `docs/architecture.services.overrides.json`. | | `pictures` | For a `.diagram` out, also write its light and dark SVG beside it. On when left out. | | `schemes` | `light-and-dark` writes `infra.svg` and `infra.dark.svg`; `auto` writes one `infra.svg` that holds both. `defaults.schemes`, then `light-and-dark`, when left out. For a Markdown out it applies to every fence of that file. | | `publish` | Where the Action publishes it (section 6). | Two sources that write one file, or a `view` the format does not draw, are S101 on their line of the settings file. A `from` that matches no file is S103. A Markdown out needs a fence that says where the diagram goes. It may be empty; the first run fills it and writes the picture block under it, as `sync` does: ````md ## Services ```diagram id=services ``` ```` ### One picture for both themes A light SVG opened on GitHub, or put in a page with a plain ``, shows its white cards in dark mode too. GitHub picks a dark file only from a `` element with a `prefers-color-scheme: dark` source. With `schemes` set to `auto`, each diagram gets one SVG that holds both schemes and switches inside the file with the same media query, so the file itself, a link to it, and a plain `` all show the scheme the reader uses. The two schemes are painted from one layout, so the picture is the same as the two files, and the one file is smaller than the two together. ```json { "defaults": { "schemes": "auto" }, "sources": [ { "name": "infra", "from": "infra/**/*.tf", "format": "terraform", "out": "docs/infra.diagram" } ] } ``` Use `auto` for any diagram people see on GitHub. A Markdown fence's picture block is then one `` instead of a ``. Switching a repository to `auto` removes each `.dark.svg` that Plotwork drew there, so no file is left behind; a check before that run reports the removal as drift. A file under that name that Plotwork did not draw is never touched. `sync` reads the same default for the fences it draws. A `git-log` source reads a file. Write it in an earlier step, such as `git log --oneline --graph -n 40 > docs/history.txt`, or the check fails on every new commit. ## 2. Generate ```sh npx @buun_group/plotwork generate ``` | Command | What it does | |---|---| | `generate` | Makes every diagram and writes the ones that changed: the `.diagram` file and its SVGs, or the fence and its picture block. | | `generate --check` | Writes nothing. Exits 1 when a diagram differs from what its files make, and prints what changed. | | `generate --source infra` | One source only. | | `generate --capture` | First writes the hand edits of each generated diagram into its overrides file. | | `generate --format github` | Each problem and each drift as a GitHub annotation. `--format json` prints the plan, `--format sarif` a SARIF report for code scanning, and `--format junit` a JUnit report. | | `generate --since origin/main` | Only the sources whose files, overrides, or diagram changed since the ref, and every source when the settings file changed. | A drifted diagram prints its changes by meaning, from the same comparison as `compare_diagrams`: reordered or reformatted lines are no change, and a renamed id is a rename. ```text drift: docs/infra.diagram (infra, terraform from infra/**/*.tf) Added 1 node (logs). Removed 1 edge (web -> db). + node logs - edge web -> db failed: 4 diagram(s) checked, 1 drifted, 0 error(s) ``` The output is the same for the same files on every machine. A generated file starts with a comment that names its source and its overrides file: ```text # Generated by plotwork generate from the source infra (terraform: infra/**/*.tf). # The next run writes over this file. Put changes in docs/infra.overrides.json, or run plotwork generate --capture # after an edit to keep it there, then run plotwork generate. ``` ## 3. Overrides An overrides file is JSON beside the diagram. It is applied after every import, so a change to the Terraform and a change to a label never fight. ```json { "$schema": "https://docs.plotwork.dev/schemas/plotwork.overrides.json", "header": { "options": { "direction": "right", "legend": "off" } }, "items": { "aws_s3_bucket.assets": { "label": "Assets bucket", "tone": "orange" }, "aws_instance.web": { "group": "compute", "type": "function" }, "aws_internet_gateway.gw": { "hide": true } }, "groups": { "compute": { "label": "Compute", "type": "app-tier", "parent": "aws_vpc.main" } }, "edges": [ { "from": "aws_instance.web", "to": "aws_lb.web", "label": "health", "arrow": "-->" }, { "from": "aws_db_instance.orders", "to": "aws_db_subnet_group.orders", "hide": true } ], "lines": [ { "statement": "node runbook step \"Runbook\" sub=\"Rotated nightly\"", "into": "compute" } ] } ``` | Section | What it changes | |---|---| | `header` | `title`, and header `options` such as `direction`, `icons`, `legend`, or `desc`. `null` takes an option away. | | `items` | For each box, group, or table, by name: `label`, `sub`, `icon`, `tone`, `type` (a node type, or a group's type), `group` (move it into a group; `null` is the top level), other `options`, and `hide`. | | `groups` | Groups to add, by the id they get: `label`, `type`, `sub`, `icon`, `parent`. Move items into them with `group`. | | `edges` | Edges to add (`from`, `to`, `label`, `arrow`, `options`), or with `hide`, the import's edges from one end to the other to take away. | | `lines` | Any other line, such as a note box, as it is, optionally `into` a group. | ### How a thing is named An entry names a thing by the name the file gives it, which does not change when the file grows. The id in the diagram can: two resources named `web` become `web` and `web_2` in the order they are read. | Format | Name | Example | |---|---|---| | `terraform`, `terraform-plan` | The resource address, after its module and with its copy's key, the same for a plan as for the files | `aws_s3_bucket.logs`, `module.network.azurerm_subnet.this["app"]` | | `bicep`, `arm` | The symbolic name, after its module | `module.data.vault`, `vnet/subnets/app`, `queue['orders']` | | `pulumi` | The logical name, or the URN when two share one | `labels` | | `cloudformation` | The logical id | `OrdersTable` | | `cdk` | The construct path, without the `Resource` that wraps it | `OrdersApi/Ordering/Handler`, `OrdersApi/Ordering` | | `compose` | `service:`, `network:`, or `volume:` and its name | `service:api` | | `kubernetes`, `helm`, `kustomize` (built) | Namespace, kind, and name; a Helm `release:` or `chart:` frame | `payments/Deployment/api`, `release:shop` | | `kustomize` (a folder) | `frame:`, `kustomization:`, `file:`, `patch:`, or `generator:` and its path | `file:base/deployment.yaml` | | `backstage` | The entity reference | `component:default/orders-api` | | `structurizr` | The DSL identifier, its path with hierarchical identifiers, or the path of names; `group:` and its path | `banking.api`, `group:banking/Payments` | | `asyncapi` | `app:` and the title, `channel:` and the address, `broker:` and the protocol | `channel:orders.placed.v1` | | `github-actions` | `job:` and the job id; `on` is the trigger | `job:test` | | `gitlab-ci` | `job:`, `stage:`, or `trigger:` and the name | `job:unit-tests`, `stage:deploy` | | `azure-pipelines` | `stage:` and its name; `job:` and `Stage.Job`, or the job alone in a file of only jobs | `job:Build.UnitTests` | | `sql`, `prisma`, `drizzle` | The table name | `users` | | The others | The id in the diagram | `orders` | The id in the diagram works for every format too, when it is stable enough for you. ### When the importer changes A new version of the importer can draw the same files differently: a Terraform stack whose local modules are now framed, or whose `for_each` copies are now drawn one by one. A stack the new rules have nothing to say about (no module, no `for_each` or `count`) is drawn exactly as before, byte for byte. When an upgrade does redraw a diagram, `generate --check` reports it as drift; run `plotwork generate` once, review the change, and commit it. ### When the file changes An entry whose thing is gone is a warning, S201, on its line of the overrides file: `Stale override for aws_s3_bucket.logs: the import no longer draws it.` An entry the diagram cannot take, such as an added group whose id is taken, is S202. Neither fails a run; remove or rename the entry. A change that leaves the diagram with an error, such as an unknown node type, is that error, on its line of the generated file. ### Keep a hand edit ```sh npx @buun_group/plotwork generate --capture --source infra ``` `--capture` compares the file you edited with what the import and the current overrides make, writes each difference into the overrides file (labels, options, types, moves, hidden and added boxes, groups, edges, and lines), and then makes the diagram again, so it comes back the same with its comment on top. A source split into several diagrams is captured part by part, each against its own file, into its one overrides file. ## 4. Fail a pull request on drift ```yaml name: diagrams on: pull_request: permissions: contents: read jobs: drift: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: persist-credentials: false - uses: plotwork-official/plotwork-action@dcec4036897b73bd3ea724b3991f4fd0ac880757 # v1.0.0-beta.14 with: command: generate ``` The samples pin the latest release of the Action by its commit. `@v1` becomes available at the first stable release. Each drifted diagram is an error annotation on its file, and the job summary has a table of every diagram: where it comes from, the nodes and edges added and removed, its status, a link to its file, and the engine's table of what changed. A job summary cannot show a picture from the runner, so the changes are written out; the pull request from write mode, and a published diagram, show the pictures. ## 5. Write the diagrams back ### Open a pull request On a push to the default branch, or on a schedule, write mode makes the diagrams again and pushes them to one branch with one pull request. Each run replaces that branch with one commit on the latest base and edits the open pull request rather than opening another. Once nothing drifts, the next run closes that pull request with a comment and deletes its branch, when the branch's last commit is the action's own. ```yaml name: diagrams-regenerate on: push: branches: [main] schedule: - cron: '0 4 * * *' permissions: contents: read jobs: regenerate: runs-on: ubuntu-latest permissions: contents: write pull-requests: write steps: - uses: actions/checkout@v7 - id: plotwork uses: plotwork-official/plotwork-action@dcec4036897b73bd3ea724b3991f4fd0ac880757 # v1.0.0-beta.14 with: command: generate mode: write pr-branch: plotwork/diagrams pr-title: 'docs: regenerate diagrams from their sources' ``` | Setting | Why | |---|---| | `contents: write`, `pull-requests: write` | To push the branch and open the pull request. | | Settings, Actions, General, "Allow GitHub Actions to create and approve pull requests" | GitHub refuses a pull request from the workflow's token without it. | | `github-token` | A pull request opened with the workflow's own token starts no workflow, so its checks do not run. Pass a GitHub App or personal token to have them run. | `on-drift: pr` refuses a `pull_request` run: such a run is on a merge commit, not on a branch. ### Commit to the branch ```yaml on: pull_request: permissions: contents: read jobs: regenerate: runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v7 with: ref: ${{ github.head_ref }} - uses: plotwork-official/plotwork-action@dcec4036897b73bd3ea724b3991f4fd0ac880757 # v1.0.0-beta.14 with: command: generate mode: write on-drift: commit commit-message: 'docs: regenerate diagrams' ``` The commit carries the trailer `Plotwork-Generated: true`. A run started by that commit does nothing, so a token that does start workflows never loops. A pull request from a fork cannot be pushed to with the workflow's token. `on-drift: fail` writes the diagrams in the workspace and fails, for a later step of your own. ## 6. Publish With `publish: true` and a key in `PLOTWORK_API_KEY`, write mode publishes each source that has a `publish` section. ```json { "name": "infra", "from": "infra/**/*.tf", "format": "terraform", "out": "docs/infra.diagram", "publish": { "organization": "acme", "folder": "Platform/Infra" } } ``` | `publish` field | Meaning | |---|---| | `alias` | A stable link of a personal key: one address that always shows the latest version. See [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md). | | `organization` | File it in this organisation's library. Left out with no `alias`, the organisation of `brand.organization` is used. An organisation key publishes only to its own organisation, and needs neither: the API names the key's organisation and the diagram goes to its library. An organisation's diagrams have no public address, so an `alias` makes no stable link there, and the run says so. | | `folder`, `name`, `theme` | Where it is filed, its name (the title when left out), and `light`, `dark`, or `auto`. | | `embedSource` | Write the diagram source into the hosted picture. Left out, it is not. | | `required` | Fail the run when it cannot be published, such as with no key. Left out, a skipped publish is a notice. | ```yaml name: diagrams-publish on: push: branches: [main] permissions: contents: read jobs: publish: runs-on: ubuntu-latest permissions: contents: write pull-requests: write steps: - uses: actions/checkout@v7 - uses: plotwork-official/plotwork-action@dcec4036897b73bd3ea724b3991f4fd0ac880757 # v1.0.0-beta.14 with: command: generate mode: write publish: true env: PLOTWORK_API_KEY: ${{ secrets.PLOTWORK_API_KEY }} ``` The key is masked in the log and never printed. The repository's defaults and `brand.organization` go with the source, so the hosted picture takes the organisation's brand and icon packs when its plan includes them. The links are in the job summary, with the pictures, and in the `published` output. ## 7. Inputs and outputs | Input | Default | Meaning | |---|---|---| | `command` | `check` | `generate` for this guide. | | `mode` | `check` | `check` fails on drift and writes nothing; `write` makes the diagrams again. | | `on-drift` | `pr` | In write mode: `pr`, `commit`, or `fail`. | | `pr-branch` | `plotwork/diagrams` | The branch `pr` pushes to. The action owns it. | | `pr-title` | `docs: regenerate diagrams from their sources` | The pull request's title. | | `commit-message` | `docs: regenerate diagrams from their sources` | The commit message, before the trailer. | | `publish` | `false` | Publish the sources that have a `publish` section, in write mode. | | `github-token` | `${{ github.token }}` | The token that pushes and opens the pull request. | | `since` | | A git ref: only the sources whose files changed since it run. Check out with `fetch-depth: 0`. | | `sarif-file`, `junit-file` | | Files to write a SARIF report (for `github/codeql-action/upload-sarif`) and a JUnit report to. | | `config`, `working-directory` | | As for every command. See [Keep diagrams in sync in CI](https://docs.plotwork.dev/diagrams-in-ci.md). | | Output | Meaning | |---|---| | `stale` | Diagrams that drifted. | | `files` | The drifted diagrams in check mode; the files written in write mode. | | `changes` | A JSON array, one entry per drifted diagram: its name, its file, the number of changes, and the sentence that names them. | | `pull-request` | The address of the pull request opened or edited, or empty. | | `published` | A JSON array of each source published: `name`, `kind` (`personal` or `organization`), `url`, `stableLink`, `svgUrl` (an organisation's picture, served to the key), `id`, and `organization`. | | `errors`, `warnings` | The problems found. | ## 8. Limits | Limit | Value | |---|---| | Sources in one settings file | 250, in a file of at most 256 KB; a larger repository gives each team a settings file of its own and runs one job per `working-directory` | | Patterns in one `from` | 20 | | Files one source reads | 2,000, at the local import limits, which only guard memory | | An overrides file | 128 KB, and 500 entries in each section | | A source too big for one picture | Written as several `.diagram` files, `infra.diagram`, `infra.2.diagram` and on; a Markdown fence holds one diagram, so such a source needs a `.diagram` out or fewer things chosen with `options` | | Parts a source no longer has | Removed with their pictures, but only a file that carries that source's generated comment | ## See also | Page | Why | |---|---| | [Importing from code and data](https://docs.plotwork.dev/importing-from-code-and-data.md) | What each format draws, and its options | | [Repository settings](https://docs.plotwork.dev/repository-settings.md) | The rest of `plotwork.config.json`: defaults, rules, and brand | | [Keep diagrams in sync in CI](https://docs.plotwork.dev/diagrams-in-ci.md) | The other commands of the GitHub Action | | [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md) | Links, stable links, and what a plan hosts | | [Organisations](https://docs.plotwork.dev/organisations.md) | Organisation keys and the shared library | | [Enterprise rollout](https://docs.plotwork.dev/enterprise-rollout.md) | A reusable workflow, required checks, code scanning, many teams in one repository, and air-gapped runners | | [plotwork-examples](https://github.com/plotwork-official/plotwork-examples) | A working repository with every format, and the smoke test of each release | --- # Enterprise rollout > Page: https://docs.plotwork.dev/enterprise-rollout. Index of every page: https://docs.plotwork.dev/llms.txt How a platform team puts diagrams as code into hundreds of repositories: one reusable workflow, a required status check, owners for the overrides files, pinned and updated releases, self-hosted runners and GitHub Enterprise Server, an air-gapped setup, and the outputs other tools read. It is for the people who run CI for many teams. ## TL;DR | Question | Answer | |---|---| | How do teams adopt it? | In GitHub Actions, call reusable workflows from a central repository, pinned by commit SHA. [plotwork-examples](https://github.com/plotwork-official/plotwork-examples) has working ones: `plotwork-check.yml`, `plotwork-scan.yml` and `plotwork-regenerate.yml`. | | What permissions does it need? | Check mode `contents: read`, and nothing else. Write mode `contents: write` and `pull-requests: write`. Code scanning adds `security-events: write`. | | Is it safe on a fork's pull request? | Check mode is: it reads no secret and makes no network request. Write mode and publishing run on a push or a schedule, never on a fork's pull request. | | How is the check enforced? | Make the job a required status check in a branch ruleset. | | How do tools read the result? | The `changes` and `files` outputs (JSON), exit codes, a SARIF file for code scanning, and a JUnit file for a test view. | | One large repository for many teams? | A settings file per team with `working-directory`, a matrix job, and `since` to check only what a pull request changed. | | Can we verify a release? | Yes. `dist/checksums.txt` in every release, and from the first release after `v1.0.0-beta.6` a signed build provenance attestation: `gh attestation verify dist/index.js --repo plotwork-official/plotwork-action`. | | Air-gapped? | The Action and the command line run offline. Only publishing calls an API; the self-hosted engine draws with no outbound request. | ## 1. Reusable workflows The central repository holds the workflows; each repository calls them. Copies to start from are in [plotwork-examples](https://github.com/plotwork-official/plotwork-examples/tree/main/.github/workflows): `plotwork-check.yml`, `plotwork-scan.yml` and `plotwork-regenerate.yml`. ```yaml name: diagrams on: pull_request: push: branches: [main] schedule: - cron: '0 4 * * *' permissions: {} jobs: check: if: github.event_name == 'pull_request' uses: acme/platform/.github/workflows/plotwork-check.yml@0123456789abcdef0123456789abcdef01234567 permissions: contents: read with: changed-only: true regenerate: if: github.event_name != 'pull_request' uses: acme/platform/.github/workflows/plotwork-regenerate.yml@0123456789abcdef0123456789abcdef01234567 permissions: contents: write pull-requests: write secrets: PLOTWORK_API_KEY: ${{ secrets.PLOTWORK_API_KEY }} ``` GitHub checks every job of a called workflow against the permissions its caller grants, even a job that would be skipped, and refuses to start the run when one asks for more. So each reusable workflow asks for one set of permissions: | Workflow | What it does | Permissions the caller grants | |---|---|---| | `plotwork-check.yml` | Fails on drift; writes nothing | `contents: read` | | `plotwork-scan.yml` | The same, and uploads code scanning alerts | `contents: read`, `security-events: write` | | `plotwork-regenerate.yml` | Makes the diagrams again and opens a pull request | `contents: write`, `pull-requests: write` | ## 2. Required status checks Add the check job's name (for the workflow above, `check / Diagrams match their files`) as a required status check in a branch ruleset on the default branch. A pull request whose diagrams drifted then cannot merge until its author runs `plotwork generate` or merges the pull request the nightly run opened. Keep a nightly run without `since`: a new release of the Action can draw pictures differently, and no file change points at that. ## 3. Owners for the overrides files Overrides are the only hand-written part of a generated diagram, so review them like code. A `CODEOWNERS` entry sends each change to the team that owns the diagram: ```text /teams/payments/docs/*.overrides.json @acme/payments /plotwork.config.json @acme/platform ``` A generated `.diagram` file needs no owner: a hand edit to it is drift, and the check fails. ## 4. Fork pull requests and pull_request_target | Event | What to run | Why | |---|---|---| | `pull_request` | `mode: check` | The token is read-only on a fork and no secret is passed. Check mode needs neither, and makes no request. | | `pull_request_target` | Nothing from this guide | It runs with the base repository's secrets and a write token. Checking out the fork's head there runs the fork's files with them. Generating diagrams never needs it. | | `push`, `schedule` | `mode: write` | The branch is the repository's own, so pushing and opening a pull request is safe. | `on-drift: pr` refuses to run on a pull request, and `on-drift: commit` needs the head branch checked out; neither works on a fork's pull request, by design. ## 5. Many teams in one repository | Shape | How | |---|---| | A settings file per team | `working-directory: teams/payments` finds `teams/payments/plotwork.config.json`. Paths in it start at that folder. | | A job per team | A matrix over the teams' folders, each job with its own `working-directory`. The reusable workflow's `concurrency` group includes the folder, so teams never wait on each other. | | Only what changed | `since: ${{ github.event.pull_request.base.sha }}` with `fetch-depth: 0`. A source runs when one of its files, its overrides file, its diagram, or the settings file changed; a deleted file runs every source. | | `paths` filters | Workflow `paths:` on the files the sources read and the settings file, so a pull request that changes neither starts no job. | | Many sources | Up to 250 sources in a settings file of at most 256 KB. Past that, split by team. | ```yaml jobs: diagrams: strategy: fail-fast: false matrix: team: [payments, search, identity] uses: acme/platform/.github/workflows/plotwork-check.yml@0123456789abcdef0123456789abcdef01234567 permissions: contents: read with: working-directory: teams/${{ matrix.team }} ``` ## 6. Outputs other tools read | Output | Contract | |---|---| | Exit code | 0 when every diagram matches its files, 1 on drift or an error (with `fail-on: never`, always 0), and the command line's 2 for a mistake in the command or the settings. | | `files` | A JSON array of paths from the repository root: the drifted diagrams in check mode, the files written in write mode. | | `changes` | A JSON array, one object for each drifted diagram, in the order of the settings: `name` (the source's name), `out` (its file from the repository root), `changes` (a count of changes by meaning), `summary` (one sentence). Fields are only ever added, never renamed or removed, within a major version. | | `published` | A JSON array, one object for each published source: `name`, `kind` (`personal` or `organization`), `url`, `stableLink` (or null), `svgUrl` (an organisation's picture, served to a key; or null), `id`, `organization`. | | `sarif-file` | SARIF 2.1.0: rule `plotwork/drift` for each drifted diagram on its first line, and one rule for each diagnostic code on its line. Upload it with `github/codeql-action/upload-sarif` and the drift shows in the Security tab. | | `junit-file` | JUnit XML: one test case for each source, failed on drift or an error. | | Command line | `plotwork generate --check --format json`, `--format sarif`, or `--format junit` prints the same to standard output. | ## 7. Pinning and updates Pin the Action by its full commit SHA, with the release in a comment, and let Dependabot move it: ```yaml - uses: plotwork-official/plotwork-action@dcec4036897b73bd3ea724b3991f4fd0ac880757 # v1.0.0-beta.14 ``` The samples pin the latest release of the Action by its commit. `@v1` becomes available at the first stable release. ```yaml # .github/dependabot.yml version: 2 updates: - package-ecosystem: github-actions directory: / schedule: interval: weekly ``` Each release's `dist/checksums.txt` holds the SHA-256 of the bundle and its notices; `sha256sum --check dist/checksums.txt` in a checkout of the release verifies them. Every release after `v1.0.0-beta.6` also carries a signed build provenance attestation for `dist/index.js` and `dist/checksums.txt`. The Action's public repository signs it with GitHub artifact attestations when the release is published, after checking the checksums, and records it in the public Sigstore transparency log. In a checkout of the release, with the GitHub CLI: ```bash gh attestation verify dist/index.js --repo plotwork-official/plotwork-action ``` To require the release workflow and the tag as well, add `--signer-workflow plotwork-official/plotwork-action/.github/workflows/release.yml --source-ref refs/tags/v`. The attestation proves the file is the one in the release tag and was signed by that repository's release workflow. The bundle is built from a private repository, so it does not describe that build. A release can draw some pictures differently, and then the Dependabot pull request that moves to it makes those pictures stale; merge it together with the regenerated diagrams, which the nightly write run opens as a pull request. ## 8. Runners, GHES, and air-gapped networks | Setup | What to know | |---|---| | Self-hosted runners | The Action runs on the runner's `node24`, with `git` on the path for write mode. It needs no other tool, and no install. | | GitHub Enterprise Server | The Action reads `GITHUB_SERVER_URL` and `GITHUB_API_URL`, so pushing and pull requests go to the server. Copy the release into the server, or sync it with GitHub Connect. | | No internet | Check and write mode make no request to Plotwork: the importers and the renderer are in the bundle. Leave `publish` off. The command line (`npx @buun_group/plotwork generate`) runs offline the same way, from an internal npm mirror. | | Hosted pictures inside the network | The [self-hosted engine](https://docs.plotwork.dev/mcp/self-hosting.md), one container, serves the drawing tools and the `/v1` drawing routes with no outbound request. It has no accounts or hosting, so publishing stays off. | ## 9. Keys and privacy | Question | Answer | |---|---| | Which key? | An organisation key publishes into the organisation's library and needs no other setting. A personal key with an `alias` publishes a stable link. | | Where does it live? | A repository or organisation secret, passed as `PLOTWORK_API_KEY` to the step that publishes, in write mode only. | | Can it leak? | It is masked with `::add-mask::` before any request, never printed, and never sent to GitHub. | | What leaves the runner? | With publishing on, the generated diagram source and the repository's defaults and brand, to the API. The files the diagrams are made from never do. | | Which API? | `PLOTWORK_API_URL`, or the hosted one. A key works only on the service that made it, and the refusal names the address it called. | ## 10. Failures to plan for | Situation | What happens | |---|---| | A file that does not parse | An annotation on its line, and that source writes nothing. The others still run. | | A source whose files are missing | S103, on its line of the settings file. | | An overrides file a source names that is not there | Warning S203; the diagram is made without overrides. | | A protected branch refuses the push | The error names the rule and says to use `on-drift: pr`, or let the token bypass it. | | A person pushed to the pull request branch | The action leaves the branch as it is and says so; it never replaces someone else's commit. | | GitHub rate limits, or a server error | Each request is tried up to four times, waiting as the answer asks. | | Two runs at once | Give each workflow a `concurrency` group by branch and folder, and never cancel a write. | | A summary too large | The summary keeps under GitHub's 1 MiB limit, shortening the change tables first; the outputs and the log keep everything. | ## See also | Page | Why | |---|---| | [Diagrams as code](https://docs.plotwork.dev/diagrams-as-code.md) | Sources, overrides, and the Action's inputs | | [Keep diagrams in sync in CI](https://docs.plotwork.dev/diagrams-in-ci.md) | The other commands of the Action | | [Self-hosting the engine](https://docs.plotwork.dev/mcp/self-hosting.md) | The engine inside your network | | [Organisations](https://docs.plotwork.dev/organisations.md) | Organisation keys and the library | --- # Diagram evidence for audits > Page: https://docs.plotwork.dev/diagram-evidence-for-audits. Index of every page: https://docs.plotwork.dev/llms.txt How to keep the network, system and data-flow diagrams an auditor asks for current between audits, and what to hand over when the auditor asks. It is for the team that owns a SOC 2, ISO/IEC 27001, PCI DSS or HIPAA audit and the platform team that runs its CI. Plotwork is a tool for producing diagram evidence; your auditor decides what satisfies a control. Plotwork itself is not certified against these frameworks, and using it does not make a company compliant. ## TL;DR | Question | Answer | |---|---| | What does Plotwork add to an audit? | Diagrams drawn from the code that builds the system, kept in step with it by a check in CI, with a record of every change: the commits, the check runs and, on Pro and Enterprise, the organisation's audit log. | | How are the diagrams kept current? | `plotwork.config.json` names the files each diagram is made from. `plotwork generate --check` fails a pull request that changes them and leaves the diagram behind. | | How is that enforced? | Make the check job a required status check in a branch ruleset on the default branch. | | Who redraws a diagram? | The GitHub Action in write mode opens a pull request with the new diagram, and a person reviews and merges it. Hand edits live in an overrides file, so the next import keeps them. | | What can I export? | The check's JSON report, SARIF and JUnit files, the git history of each diagram, and on Enterprise the audit log as CSV or JSON Lines. | | Does the check send anything anywhere? | No. Check mode reads no secret and makes no network request. | | How do I keep the tool itself fixed? | Pin the Action to a commit SHA and the command line to an exact version, and verify a release with its checksums. | ## 1. What auditors ask for The frameworks below commonly call for diagrams as evidence. The clause is where an auditor most often asks; how the diagrams are judged is between you and your auditor. | Framework | Where diagrams are commonly requested | What to keep current | |---|---|---| | SOC 2 | The system description (description criteria DC 200: infrastructure, software, data and boundaries), and change management (CC8.1) | Network and data-flow diagrams of the system in scope, and a record that changes to it are reviewed | | ISO/IEC 27001:2022 | Annex A 5.9 (inventory of information and other associated assets) and 8.20 (networks security) | Diagrams of the assets in scope and how the networks that hold them are separated | | PCI DSS v4.0 | Requirement 1.2.3 (an accurate network diagram is maintained) and 1.2.4 (an accurate data-flow diagram is maintained) | The network diagram and the account data-flow diagram, as the environment changes | | HIPAA Security Rule | The risk analysis, 45 CFR 164.308(a)(1)(ii)(A) | Where electronic protected health information is stored and how it moves | ## 2. Draw the diagrams from the code Add a source for each diagram to `plotwork.config.json` at the root of the repository: the files it is made from, their format, and where the diagram is written. ```json { "sources": [ { "name": "infra", "from": "infra/**/*.tf", "format": "terraform", "out": "docs/infra.diagram" } ] } ``` Run `npx @buun_group/plotwork generate` once and commit what it writes: the diagram, its pictures in light and dark, and nothing else. Terraform, Kubernetes manifests, Docker Compose, OpenAPI, SQL, Prisma and the other formats of [Importing from code and data](https://docs.plotwork.dev/importing-from-code-and-data.md) are read. Nothing is fetched and no code in the files is run. A change you make by hand, such as a clearer label or a group, goes in the overrides file beside the diagram (`docs/infra.overrides.json`), never in the diagram itself, so the next import keeps it. [Diagrams as code](https://docs.plotwork.dev/diagrams-as-code.md) has every option. ## 3. Make the check a required status check Run the check on every pull request, here in GitHub Actions: ```yaml name: diagrams on: pull_request: permissions: contents: read jobs: drift: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: persist-credentials: false - uses: plotwork-official/plotwork-action@dcec4036897b73bd3ea724b3991f4fd0ac880757 # v1.0.0-beta.14 with: command: generate ``` The samples pin the latest release of the Action by its commit. `@v1` becomes available at the first stable release. Then, in the repository's settings, add a branch ruleset on the default branch that requires the `drift` job to pass. A pull request that changes the infrastructure and leaves a diagram behind cannot merge until the diagram is redrawn. | Setting | Why | |---|---| | `contents: read` only | Check mode needs nothing else. It reads no secret and makes no network request, so it is safe on a pull request from a fork. | | A required status check | Without it a failed check is a warning a person can merge past. | | A nightly run without `since` | A new release of the Action can draw a picture differently, and no file change points at that. | To redraw the diagrams for you, add the write mode workflow from [Diagrams as code](https://docs.plotwork.dev/diagrams-as-code.md): on a push to the default branch or on a schedule, the Action opens a pull request titled `docs: regenerate diagrams from their sources` from the branch `plotwork/diagrams`. A person reviews and merges it, which is the review an auditor looks for. ## 4. Keep the evidence Each of these is made by the run itself; keep them where your audit evidence lives. | Evidence | Where it comes from | How to keep it | |---|---|---| | The check passing on a merge | The required status check on the pull request | Your repository host keeps it with the pull request | | What the diagram looked like, and when it changed | The diagram and its pictures are files in the repository | `git log -- docs/infra.diagram` lists every version with its commit and time | | A machine-readable record of the run | `report-file` (JSON), `sarif-file` (SARIF 2.1.0) and `junit-file` (JUnit XML) inputs of the Action, or `plotwork generate --check --format json` | Upload them as build artifacts, or send the SARIF file to code scanning | | Who changed what in the organisation | The organisation's audit log, on Pro and Enterprise | On Enterprise, export it as CSV or JSON Lines, have your SIEM poll it with a key that can only read it, or [send each event as it happens](https://docs.plotwork.dev/siem-delivery.md) to Datadog, Dynatrace, Splunk, Microsoft Sentinel or a webhook | The audit log records members, roles, keys, published diagrams, locks, drift reports from CI and changes made by Plotwork support. It never records a diagram's source, a key's value, a token or a full IP address. [Audit log](https://docs.plotwork.dev/audit-log.md) has the event, how long each plan keeps it, and the export. ## 5. Pin the versions An audit asks whether the tool that made the evidence could change under you. Fix it: | What | How | |---|---| | The GitHub Action | Pin it to its full commit SHA with the release in a comment, and let Dependabot move it in a pull request you review. | | The command line | Run an exact version, such as `npx --yes @buun_group/plotwork@0.1.0-beta.13 generate --check`. | | A release | Each Action release has `dist/checksums.txt`, the SHA-256 of the bundle and its notices; `sha256sum --check dist/checksums.txt` verifies it. The npm packages are published with provenance. | [Enterprise rollout](https://docs.plotwork.dev/enterprise-rollout.md) has the Dependabot file and the reusable workflow for many repositories. ## 6. What to show an auditor | Ask | Show | |---|---| | A current network or data-flow diagram | The diagram's picture from the default branch, and the commit it was last changed in | | Evidence the diagram is accurate | The settings that name the files it is drawn from, and the required check that fails when it falls behind | | Evidence changes are reviewed | The pull requests that changed the infrastructure, each with the check passing, and the regenerate pull requests a person merged | | A history of the diagram | `git log` of the diagram, or its versions in the portal when it is published to the organisation | | Who had access and what they did | The organisation's members and roles, and the audit log export | | How the tool is controlled | The pinned Action and command line versions, the checksums, and that check mode makes no network request | ## See also | Page | Why | |---|---| | [Diagrams as code](https://docs.plotwork.dev/diagrams-as-code.md) | Sources, overrides, check and write mode | | [Keep diagrams in sync in CI](https://docs.plotwork.dev/diagrams-in-ci.md) | The Action's commands and inputs | | [Enterprise rollout](https://docs.plotwork.dev/enterprise-rollout.md) | Required checks, reports and pinning across many repositories | | [Audit log](https://docs.plotwork.dev/audit-log.md) | What the organisation's log records, and its export | | [Privacy policy](https://docs.plotwork.dev/privacy-policy.md) | What the hosted service stores, and what it never does | --- # Slack, email, Confluence, and Notion > Page: https://docs.plotwork.dev/wikis-and-chat. Index of every page: https://docs.plotwork.dev/llms.txt How to put a Plotwork diagram where a picture is wanted outside a repository: a PNG for a chat or an email, and paste-ready text for Confluence and Notion. Nothing here needs an account with those services, a token, or a paid API. ## TL;DR | Where | What to use | How | |---|---|---| | Slack, Teams, email | A PNG | Open `pngUrl` from the agent's answer. The page draws the diagram and saves the PNG. Attach the file. | | Anywhere that takes an image link | The hosted SVG link | Ask the agent to publish the diagram (`publish_diagram`), and paste the link. | | Confluence Cloud | Wiki markup | `npx @buun_group/plotwork export diagram.md --format confluence-wiki`, then Insert, then Markup. | | Confluence through its REST API, or a page source editor | Storage format | `npx @buun_group/plotwork export diagram.md --format confluence`. | | Notion | Markdown | `npx @buun_group/plotwork export diagram.md --format notion --image `, then paste. | | Does Plotwork draw the PNG on a server? | No | The PNG is drawn in the browser that opens the link. Plotwork's servers never see the diagram. | | Does Plotwork publish to Confluence or Notion for me? | No | Doing so would mean storing a token for your workspace. Plotwork stores none. You paste. | ## 1. A PNG for a chat or an email Every valid answer of `draw_diagram`, `draw_from_pattern`, and `convert_mermaid` has two links: | Field | What opening it does | |---|---| | `viewUrl` | Shows the diagram in the browser. See [Viewing diagrams](https://docs.plotwork.dev/viewing-diagrams.md). | | `pngUrl` | The same link with `&download=png` at the end. It shows the diagram and saves it as a PNG, in the theme the page shows, at twice its size. | `pngUrl` is a link to open, not an image address to embed: the PNG is made in your browser from the source in the link, after the `#`, which a browser never sends to a server. Open it, then attach the file it saved. `&download=svg` saves the SVG the same way. A diagram too large for a link has neither field, and the answer says why; publish it instead. ## 2. A hosted link A published diagram has a fixed address on `cdn.plotwork.dev`, an SVG that any page, wiki, or chat that shows images can load. Ask the agent to publish the diagram, or see [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md). Use the link as the image of the exports below, and the wiki fetches the picture itself. A picture link shows one drawing. For a page that should keep up with edits, publish with an alias and use the stable link instead: it always shows the latest version published under that name. See [Links that always show the latest version](https://docs.plotwork.dev/hosted-diagrams.md#8-links-that-always-show-the-latest-version). ## 3. Confluence The command line writes the picture and the diagram's source, folded in an expand block under it, so the page keeps what the picture was drawn from. | Format | Flag | Where it goes | |---|---|---| | Wiki markup | `--format confluence-wiki` | Confluence Cloud: in the editor choose Insert, then Markup, pick Confluence wiki, and paste. | | Storage format (XHTML) | `--format confluence` | The body of a page sent to the Confluence REST API with the representation `storage`, or a page source editor. | Where the picture comes from: | You pass | The text refers to | You do | |---|---|---| | `--image ` | The link | Paste. Confluence loads the picture from the link. | | Nothing | An attachment named after the diagram | The command writes the SVG beside the text. Attach it to the page first (Insert, then Files and images), then paste. | The picture is never embedded in the text. Confluence's storage format names an image only by attachment or by address, and it does not show an image written into a page as a `data:` address. ```sh npx @buun_group/plotwork export docs/architecture.md --format confluence-wiki --image https://cdn.plotwork.dev/d/.svg ``` The command reads the first diagram of the file: a diagram file, or the first `diagram` fence of a Markdown file. `--no-source` leaves the source out, and `--theme dark` writes the dark picture. ## 4. Notion ```sh npx @buun_group/plotwork export docs/architecture.md --format notion --image https://cdn.plotwork.dev/d/.svg ``` Paste the text into a page. Notion turns the image link into an image block and the source into a code block. With a stable link as the `--image`, the image block shows each new version the next time the page loads. To add only the picture, type `/image`, choose Embed link, and paste the link. Without `--image`, the command writes the SVG beside the text: paste, delete the broken image, and drag the SVG onto the page in its place, because Notion cannot load a file named in pasted text. ## 5. Why there is no direct publishing Writing to a Confluence or Notion workspace needs an OAuth app registered with each service, a server that holds every person's access token, and a way to refresh and revoke those tokens. Plotwork holds no such token and keeps no diagram content, so you paste the text yourself. The command line runs on your machine and sends nothing. ## See also | Page | Why | |---|---| | [Viewing diagrams](https://docs.plotwork.dev/viewing-diagrams.md) | What the view link is and what it sends | | [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md) | Links that load as images anywhere | | [Keep diagrams in sync in CI](https://docs.plotwork.dev/diagrams-in-ci.md) | The command line, and the check for pull requests | | [Privacy policy](https://docs.plotwork.dev/privacy-policy.md) | What Plotwork stores, and what it does not | --- # Viewing diagrams > Page: https://docs.plotwork.dev/viewing-diagrams. Index of every page: https://docs.plotwork.dev/llms.txt Where you see a diagram an agent drew, in each place you work with an agent, and what is sent where when you do. An agent reads the check and the outline; you want the picture. ## TL;DR | Question | Answer | |---|---| | How do I see a diagram from a terminal agent? | Ask the agent for the view link. Every valid answer of `draw_diagram`, `draw_from_pattern`, and `convert_mermaid` has a `viewUrl`. Open it in a browser. | | What does the view link show? | The diagram on a canvas with zoom and fit, light and dark, the source one tab away, and downloads as SVG and PNG. It works on a phone. | | Is the diagram sent to Plotwork when I open the link? | No. The source is in the part of the link after `#`, which a browser never sends to a server. The page draws the diagram in your browser. | | Can the link save a PNG for Slack or email? | Yes. `pngUrl` in the same answer is the view link with `&download=png`: it draws the PNG in your browser and saves it. `svgUrl` does the same for an SVG. See [Slack, email, Confluence, and Notion](https://docs.plotwork.dev/wikis-and-chat.md). | | How does an agent save the diagram into my repository? | It asks for the SVG with `svg` set to `always` and `embedSource` set to true, and writes it. Once the command line release that reads view links is on npm, the answer also carries a `saveCommand` the agent runs instead, so the SVG never passes through the model. See [Save a diagram into a repository](#save-a-diagram-into-a-repository). | | Who can read the diagram? | Anyone you give the link to. The source is in the link itself, so treat a view link like the diagram's text. | | Does it work in Claude, Claude Desktop, VS Code, or ChatGPT? | In a client that supports MCP Apps, the drawing appears inline under the tool call, with light and dark, zoom, and a button that opens the view link. A client without MCP Apps shows nothing extra. | | Can the local server open the browser for me? | Yes. Set `PLOTWORK_OPEN=1` in the server's environment. Each drawing is written to your temporary folder and opened in your default browser. | | Can I present a diagram or put it in slides? | Yes. Present shows it full screen one part at a time, and Export makes a PNG on every plan, and a PDF or a PowerPoint deck with one slide a step on Pro. See section 7. | | Is there a size limit? | A view link is never longer than 32,000 characters. A diagram whose link would be longer gets no `viewUrl`, and the answer says why. Publish it instead. | ## 1. Where you see a diagram | Where you work | What you see | How | |---|---|---| | A terminal agent, such as Claude Code or Codex | The view link | Ask the agent to give you `viewUrl`, then open it. The tool descriptions tell the agent to give it to you when you want to look. | | Claude, Claude Desktop, VS Code, ChatGPT | The drawing inline, under the tool call | The drawing tools name an MCP Apps view. The client shows the SVG the answer already holds. | | A phone | The view link | The page fits the diagram to the screen; pinch to zoom and drag to move. | | Any browser | The view page | `app.plotwork.dev/view` draws the diagram from the link. | | The local server or the npm package | A tab in your default browser | Set `PLOTWORK_OPEN=1`. | | A README or a pull request | The SVG file | The agent runs `saveCommand`, or calls `sync_markdown`. See [Save a diagram into a repository](#save-a-diagram-into-a-repository) and [Markdown and GitHub](https://docs.plotwork.dev/markdown-and-github.md). | | A link to share that does not carry the source | A hosted link | Call `publish_diagram` on the hosted server. See [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md). | ### Save a diagram into a repository The hosted server cannot write to your computer, so the agent writes the file. Today it asks `draw_diagram` for the SVG with `svg` set to `always` and `embedSource` set to true, and writes the SVG it gets. The answer's `next` and the server's instructions say so. | Field | What it is | Who uses it | |---|---|---| | `svgUrl` | The view link with `&download=svg`. Opening it draws the SVG in your browser and saves it to your downloads. | A person who wants the file. It is not a file address: `curl` gets the viewer page, not the SVG. | | `pngUrl` | The view link with `&download=png`. | A person who wants a PNG for chat or email. | | `saveCommand` | `npx -y @buun_group/plotwork@ render "" --out .svg`. The command line decodes the diagram from the link, draws it, and writes the SVG with the source inside, with no request and no account. | An agent with a shell, such as Claude Code or Codex. It is in the answers of `draw_diagram`, `draw_from_pattern`, `convert_mermaid`, `import_source`, and `edit_diagram` only once the command line release that reads view links is on npm. Then `next` and the server's instructions tell the agent to run it, changing `--out` to the path it wants. | When the command would be too long for a Windows command line there is no `saveCommand`, and `next` says to write the source to a `.diagram` file and render that file with the same command line. Until that release, an agent without the command asks `draw_diagram` for the SVG with `svg` set to `always` and `embedSource` set to true, and writes the file itself. ## 2. The view link A view link looks like `https://app.plotwork.dev/view#v1.` followed by the diagram's source, compressed and written in letters, digits, `-` and `_`. | Part | What it is | |---|---| | `https://app.plotwork.dev/view` | The viewer page in the portal. It needs no sign-in and asks the API for nothing. The preview environment's server points at `app-preview.plotwork.dev`. | | `#` | Everything after it is the fragment. A browser keeps the fragment to itself: it is not in the request for the page, in the referrer, or in the server's logs. | | `v1.` | The version of the format, so a link made today still opens after the format changes. | | The rest | The source as UTF-8, compressed with raw deflate and written in base64url. | The page loads the drawing engine, reads the fragment, checks the source, and draws it in light and dark. The theme switch picks which picture you see. The Source tab shows the text and copies it, with a prompt for an agent to change it. The SVG download carries the source, so `read_diagram` can give it back later. | Situation | What the page says | |---|---| | The link has no fragment | There is no diagram in the link. | | Part of the link is missing or changed | The link is damaged. | | The fragment expands past the largest possible diagram | The diagram is too large to open. | | The source has mistakes | Each mistake, with its line and column. | The page is kept out of search engines. The first visit downloads the drawing engine, about 2.4 MB compressed, which the browser then caches. ## 3. What is sent where | Step | What is sent | To whom | |---|---|---| | The agent draws the diagram | The source, in the tool call | The MCP server you use. The hosted server works on it in memory and keeps nothing. The local server never sends it anywhere. | | The answer carries `viewUrl` | The link, in the tool answer | Your agent and its model, like the rest of the answer. | | You open the link | A request for the viewer page, without the fragment | Plotwork's host at Cloudflare, which sees an ordinary page request: your IP address and browser, never the diagram. | | The page draws the diagram | Nothing | The drawing happens in your browser. | | You download an SVG or PNG | Nothing | The file is made in your browser. | | You open `pngUrl` or `svgUrl` | Nothing more than for `viewUrl` | The page draws the file in your browser and saves it. | | The agent runs `saveCommand` | Nothing | The CLI decodes the link and draws the SVG on your machine. `npx` downloads the CLI from npm the first time. | | You share the link | The source, in the link | Whoever you give it to, and any service the link passes through, such as a chat app that stores messages. | The [privacy policy](https://docs.plotwork.dev/privacy-policy.md) says the same in its section on view links. ## 4. The inline view in MCP Apps clients MCP Apps is an extension of the Model Context Protocol that lets a tool name an HTML view, which a supporting client shows in a sandboxed frame. `draw_diagram`, `draw_from_pattern`, and `convert_mermaid` name the view `ui://plotwork/viewer.html` in their `_meta.ui.resourceUri`. | Rule | Why | |---|---| | The view shows the SVG the answer already holds | No extra call and no extra work on the server. | | A picture is shown as an image, never as markup | Nothing in a diagram can run in the frame. | | The view makes no request | The client's default policy for a view allows none, and the view needs none. | | A client without MCP Apps ignores it | The tool answers are the same in every client. | | An answer with `svg` set to `never` has no picture | The view says so, and its button opens the view link instead. | ## 5. Opening the browser from the local server The npm package and the stdio server read two variables at start-up. Neither is used by the hosted server. | Variable | Effect | |---|---| | `PLOTWORK_OPEN=1` | After each drawing with pictures, the server writes `light.svg`, `dark.svg`, and a small `index.html` to a new folder in your system's temporary folder, opens the page in your default browser, and adds the folder to the answer's `next`. No network request is made. The folder is left for your system to clean up. | | `PLOTWORK_VIEWER_URL` | The viewer page that `viewUrl` points at, such as `http://localhost:3104/view` for a portal you run yourself. Unset, it is `https://app.plotwork.dev/view`. | Building the view link sends nothing anywhere. Only opening it asks the viewer's host for the page. ## 6. For people who build clients | Fact | Detail | |---|---| | `viewUrl` is optional | It is present only for a valid diagram whose link fits in 32,000 characters. | | Output schemas are open | Every object in a tool's output schema allows fields it does not list, so a client that kept the schema from an earlier version still accepts an answer with a new field. A client that connected before this change kept a closed schema: it accepts answers again once it reconnects. | | The REST API adds it too | `POST /v1/diagrams/draw`, `/v1/diagrams/convert-mermaid`, and `/v1/patterns/draw` answer with the same `viewUrl`. | ## 7. Presenting and exporting The viewer's toolbar has **Present** and **Export**. Both work on the view page, on a hosted diagram's page, and on an organisation's diagram page. Every file is made in your browser, and nothing is sent to Plotwork. ### Presenting a diagram step by step Present fills the screen with the diagram and shows it one part at a time. | Kind | The steps | |---|---| | Flow, state, swimlane, C4, layers, Gantt, mind map, Sankey | The first box no line comes into, then the boxes it leads to, in reading order. Each box appears with the lines from the boxes already shown and their labels. A group appears with its first box. | | Sequence | The participants first, then one message a step, in the order they are sent. | | Any other kind | The whole diagram, as one step. | The camera follows the presentation: each step is framed on what is shown so far, enlarged up to 2.5 times, and glides to the next frame. The last step shows the whole diagram. **Whole diagram** in the bar keeps the whole diagram in view instead, and the page remembers your choice in this browser. A diagram with more than 40 boxes shows several boxes a step, so a presentation never has more than 40 steps. Nothing moves between steps: each part is where it is in the whole diagram. | Key or gesture | What it does | |---|---| | Right arrow, down arrow, Space, Page Down, Enter | The next step. A presentation remote's "next" is Page Down. | | Left arrow, up arrow, Page Up, Backspace | The step before. | | Home, End | The first step, the whole diagram. | | Escape, or the close button | Stops presenting. | | Tap | The next step. A tap on the left third of the screen goes back. | | Swipe left, swipe right | The next step, the step before. | A screen reader hears the step number and the names of what it adds. With reduced motion turned on in your system settings, each step appears at once instead of fading in. A hosted diagram's page reads the diagram's text from its public link to find the steps. Where the page may not read it, the diagram is presented whole, as one step. ### Exporting a file | Format | What you get | Plan | |---|---|---| | PNG | A picture at one, two or four times the diagram's size, on its light or dark paper. The page shows the size in pixels before you download. A very large diagram is drawn smaller, so the picture stays within what a browser can draw. | Every plan | | PDF | One vector page the size of the diagram, sharp at any zoom and when printed. The text is set in Helvetica and fitted to the width the diagram was drawn for, so no word leaves its box. Shadows are left out, and a script other than Latin does not print: use the PNG for those. | Pro | | PowerPoint | A 16:9 deck with an optional title slide, then one slide for each presentation step, framed the way the presentation frames it, the last showing the whole diagram. Each slide holds a sharp PNG, centred, and its speaker notes name what the step adds. | Pro | A diagram that follows your light or dark setting asks which colours the file keeps, and starts from the ones on screen. A diagram drawn for one scheme keeps its own. The plan is the plan of the person exporting. For an organisation's diagram it is the plan of the organisation's owner, the same as its other limits. Someone with only a link who is not signed in can present the diagram and download a PNG, and the Export box says how to sign in for the rest. ## See also | Doc | Why | |---|---| | [MCP server](https://docs.plotwork.dev/mcp/overview.md) | The tools, the hosted and local servers, and their settings | | [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md) | A public link to the picture, without the source | | [Plans and billing](https://docs.plotwork.dev/plans-and-billing.md) | What the Pro plan adds, PDF and PowerPoint downloads among it | | [Editing an existing diagram](https://docs.plotwork.dev/editing-existing-diagrams.md) | Reading the source back from a picture | | [Privacy policy](https://docs.plotwork.dev/privacy-policy.md) | What is stored, what is not, and what a view link carries | | [draw_diagram](https://docs.plotwork.dev/mcp/tools/draw-diagram.md) | The tool as the server describes it | --- # Editing an existing diagram > Page: https://docs.plotwork.dev/editing-existing-diagrams. Index of every page: https://docs.plotwork.dev/llms.txt How an agent, or you, changes a diagram that already exists as a Plotwork picture: a hosted link, an `.svg` file in a repository, or a `diagram` fence in a Markdown file. The picture can give its diagram source back only if it was saved with the source inside it, so this guide also says when to save it that way. ## TL;DR | Question | Answer | |---|---| | How do I edit a picture I only have as a link or a file? | Call `read_diagram` with its link as `url` or its text as `svg`, edit the source it returns, call `draw_diagram`, then write the file again or call `publish_diagram`. | | When can a picture give its source back? | When it was saved with `embedSource` set to true, or written by `sync_markdown`. A picture without the source says so and returns its title, outline, and labels to rebuild it from. | | When should I set `embedSource`? | Whenever you save an SVG into a repository, or publish a link someone may want to edit later. It is off by default. | | Is the embedded source private? | No. The source is part of the picture, so anyone who has the file or the link can read it. For a diagram in an organisation, every member who can open it can. | | Does an edit keep the same link? | No. A hosted link is one drawing. An edited diagram is a new picture with a new link, so replace the old link where it is used. | | Does reading use the network? | Only when you give `read_diagram` a link. Reading the text of a file touches no network, and the local server still makes no network requests by default. | | Which links can it read? | Pictures on `cdn.plotwork.dev` and `cdn-preview.plotwork.dev`, over https, at a path like `/d/<22 characters>.svg`. Nothing else is fetched. | | Can I read one without an agent? | Yes, over HTTP. `POST /v1/diagrams/read` takes the text of a picture or the link of a hosted one and answers with its source. Calling it directly needs the [Pro plan](https://docs.plotwork.dev/plans-and-billing.md) and an API key. | ## 1. How a picture carries its source A Plotwork picture is an SVG file. When it is drawn with the source embedded, the source sits in one `<metadata data-format="buun-diagram">` element of the file, written as XML text: `&`, `<`, and `>` are escaped, so any source fits, and a plain fetch of the file shows it. `read_diagram` reads that element back and returns the exact text that was written, down to its quotes, blank lines, and line endings. | How the picture was made | Source inside? | |---|---| | `sync_markdown` on a Markdown file | Yes. It always writes it. | | `draw_diagram`, `draw_from_pattern`, `convert_mermaid`, or `render_diagram` | Only with `embedSource` set to true, or with `detail` set to `full` where the tool has it. | | `publish_diagram` or `POST /v1/diagrams/publish` | Only with `embedSource` set to true. | | A picture drawn before this existed, or saved by another tool | No. | A hosted picture also carries a stored diagram id, in `data-plotwork-id` on its root element. `read_diagram` returns it as `id`, so you know which stored diagram the file came from. ## 2. Edit a hosted link You have `https://cdn.plotwork.dev/d/<22 characters>.svg` in a README, a ticket, or a chat, and a request to change it. | Step | What to do | |---|---| | 1 | Call `read_diagram` with `url` set to the link. It returns `source`, `kind`, `id`, and the check of the source, with every error's line, column, and hint. | | 2 | Edit `source`. Keep what the person did not ask you to change. | | 3 | Call `draw_diagram` with the edited source and `embedSource` set to true. Fix every error it returns, then read the outline to check the diagram says what was meant. | | 4 | Call `publish_diagram` with the same source and `embedSource` set to true, and the same `folder` and `name` as before if there were any. Tell the person that the source is part of the picture. | | 5 | Replace the old link with the new one wherever it is used, such as the README or the ticket. The answer carries a Markdown image to paste. | If the picture has expired or was deleted, `read_diagram` returns status `unreachable` and says so. Then you need the source from somewhere else, such as a `diagram` fence or a `.diagram` file. ## 3. Edit an `.svg` file in a repository You have a file such as `docs/assets/login-flow.svg` and a request to change it. | Step | What to do | |---|---| | 1 | Read the file, then call `read_diagram` with its text as `svg`. | | 2 | Edit the source. | | 3 | Call `draw_diagram` with the edited source and `embedSource` set to true. | | 4 | Write the returned `svg` over the file. Draw the dark theme too if the repository has a dark file next to it. | | 5 | Show the person the change, or the new picture. | If you keep the edited source in a `.diagram` file next to the picture, the next edit starts from the file, not from the picture. ## 4. Edit a `diagram` fence in Markdown If the picture sits under a `diagram` fence in a Markdown file, the fence is the source and nothing needs reading back. Edit the fence, then call `sync_markdown` with the document. It returns the rewritten pictures and the block between the markers, and it embeds the source in each picture. See [Diagrams in Markdown and on GitHub](https://docs.plotwork.dev/markdown-and-github.md). When a repository has both a fence and a picture, edit the fence. The next sync rewrites the picture from it. ## 5. When the picture has no source A picture saved without `embedSource` has nothing to read back. `read_diagram` does not fail. It answers with status `no-source`, and: | Field | What it holds | |---|---| | `message` and `next` | What happened, and what to look for next. | | `picture.title` | The title of the diagram. | | `picture.outline` | The plain-language outline the picture carries, one entry for each line: its groups, nodes, and connections. | | `picture.labels` | The text painted on the picture, each label once, in painted order. | Use them in this order: 1. Look for the source where the picture came from: a `diagram` fence in the Markdown file that shows it, or a `.diagram` file next to it. 2. If there is none, rebuild the diagram from the title, outline, and labels, call `draw_diagram`, and compare the new outline with the old one. 3. Save the result with `embedSource` set to true, so the next edit starts from the source. The same answer shape covers a text that is not an SVG (`not-a-picture`), a source that was changed by hand after it was written (`damaged`), a link on another host (`refused`), and a link that does not answer (`unreachable`). Each is a result with a reason and a next step, never an exception. ## 6. Privacy The embedded source is stored in the picture, so it is stored wherever the picture is. This is the one case in which Plotwork keeps diagram source, and it happens only when `embedSource` is set to true. | Where the picture goes | Who can read the source | |---|---| | A file in your repository | Everyone who can read the repository, and everyone who can fetch the file. | | A hosted link | Anyone who has the link. Deleting the diagram deletes the source with the picture, and a cached copy can stay visible for up to an hour. | | An organisation's private library | Every member who can open the diagram. | | A diagram only drawn, never saved | Only you. `draw_diagram` returns the picture and stores nothing, even with `embedSource` set to true. | Nothing sets `embedSource` for you. An agent should set it when the person is likely to edit the diagram later, and should say so when it publishes. Leave it off for a diagram whose labels are fine to show but whose source you want to keep to yourself. See the [privacy policy](https://docs.plotwork.dev/privacy-policy.md). ## 7. A new link on every edit A hosted link is one drawing and it never changes in place. Publishing the same diagram again returns the same link. Publishing an edited diagram stores a new picture, so it gets a new link, and the old link keeps showing the old drawing until it expires. The same diagram published with `embedSource` and without it is also two pictures with two links. So an edit is two steps: publish, then replace. An agent that edits a README should replace every use of the old link in the same change. ### On GitHub GitHub fetches each image through its own proxy and caches it by address. A new link is a new address, so the new drawing shows at once. A README that still holds the old link keeps showing the old drawing, and that is why the old link has to be replaced. The CDN tells caches to keep a picture for up to an hour, which only matters for an address that changes in place, and a Plotwork link does not. ## 8. For agents Both servers tell a model these steps, in their instructions, in the overview that `get_diagram_syntax` returns (under `editing`), and in the tool descriptions: 1. To change an existing Plotwork SVG, call `read_diagram` with its `url` or its text first. 2. Edit the source it returns, then call `draw_diagram`. 3. Write the file again, or call `publish_diagram` again. The link changes, so replace the old link. 4. Set `embedSource` to true whenever you save an SVG into a repository or publish a link someone may want to edit later. ## See also | Doc | Why | |---|---| | [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md) | Publishing, links, folders, plans, and how long a link lasts | | [Diagrams in Markdown and on GitHub](https://docs.plotwork.dev/markdown-and-github.md) | The `diagram` fence, `sync`, and the pictures GitHub shows | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop an agent follows to write a diagram | | [Privacy policy](https://docs.plotwork.dev/privacy-policy.md) | What is stored when you publish, and what embedding the source changes | | [read_diagram](https://docs.plotwork.dev/mcp/tools/read-diagram.md) | The tool as the server describes it | --- # Migrating from Mermaid > Page: https://docs.plotwork.dev/migrating-from-mermaid. Index of every page: https://docs.plotwork.dev/llms.txt How to move existing Mermaid diagrams to Plotwork with a converter that is deterministic, and how an AI agent finishes the job. It is written for the agent first: every step names the tool to call and what the result means. People can follow the same steps over the HTTP API. ## TL;DR | Question | Answer | |---|---| | What converts? | Flowcharts (`flowchart`, `graph`), `sequenceDiagram`, `erDiagram`, `stateDiagram`, `classDiagram`, `gantt`, `gitGraph`, `timeline`, `journey`, `mindmap`, `treemap-beta` (and `treemap`), `pie`, `quadrantChart`, `C4Context`, `C4Container`, `C4Component`, `C4Dynamic`, `C4Deployment`, `sankey-beta` (and `sankey`), `xychart-beta` (and `xychart`), and `treeView-beta` (and `treeView`). Each becomes the Plotwork kind of the same name: flow, sequence, schema, state, class, gantt, git, timeline, journey, mindmap, treemap, pie, quadrant, c4, sankey, xychart, or tree. | | What does not? | Kanban, radar, and the other kinds in [the list below](#mermaid-kinds-plotwork-does-not-draw). One diagnostic names the kind and says what to do instead. | | One diagram | Call `convert_mermaid` with the Mermaid text. It returns the Plotwork source, the diagnostics, the validation, and an outline. | | A whole document | Call `sync_markdown` with `convertMermaid: true`. It returns the rewritten document and the picture files to write. | | HTTP API | `POST /v1/diagrams/convert-mermaid` for one diagram and `POST /v1/markdown/sync` for a document. Calling them directly needs the [Pro plan](https://docs.plotwork.dev/plans-and-billing.md) and an API key. | | What is lost? | Strokes, text colours, themes, links, tooltips, activations, and a few more. A fill that has a [tone](#fills-become-tones) is kept as that tone. Every loss is reported with a diagnostic and a hint. Nothing is dropped silently. | | Is it a model? | No. The converter is plain code. The same Mermaid always gives the same Plotwork. | | What is left for the agent? | Read every diagnostic, give each node a type that means something, add a title and icons, then validate and render. | ## Why migrate GitHub already draws Mermaid fences in the page, so Mermaid works with no setup. Plotwork gives you these in return for the move: | What you get | How | |---|---| | One look in light and dark | The theme owns every colour. There is no `classDef`, `style`, or `%%{init}%%` to vary from one diagram to the next. An author chooses a node type or a tone for meaning. | | A static picture | The output is an SVG file in the repository. It needs no JavaScript, no browser, and no network to display. | | Output that diffs cleanly | The same source gives the same bytes, so a change to a diagram shows as a small change in the diff. | | Errors an agent can repair | Every mistake has a line, a column, and a hint, and all of them come back in one pass. | | A closed icon vocabulary | An icon is `set:name` from a fixed list, and an unknown name is an error with a suggestion. | | Text that never overflows | Boxes grow to fit their text, with the same padding on every side. | ## When not to migrate Keep the Mermaid fence when the diagram is one of the kinds in the next table. GitHub keeps drawing it. A fence that cannot be converted is left exactly as it was, so a document can hold both kinds. ### Mermaid kinds Plotwork does not draw | Mermaid diagram | Closest Plotwork kind | What to do | |---|---|---| | requirement diagram | `flow` | Redraw it as a flow diagram with one node per requirement and element, or keep it as Mermaid in its own mermaid fence. | | block diagram | `flow` | Redraw it as a flow diagram with a group for each block that holds others, or keep it as Mermaid in its own mermaid fence. | | packet diagram | `schema` | Redraw it as a schema diagram with one column per field, or keep it as Mermaid in its own mermaid fence. | | kanban board | none | Keep it as Mermaid in its own mermaid fence; GitHub draws it. A Markdown task list per column is the plain alternative. | | architecture diagram | `flow` | Redraw it as a flow diagram: a group for each Mermaid group and a node with a type and an icon for each service. Or keep it as Mermaid in its own mermaid fence. | | ZenUML sequence diagram | `sequence` | Redraw it as a Plotwork sequence diagram (actors, messages, and alt, opt, loop, par blocks), or keep it as Mermaid in its own mermaid fence. | | radar chart | none | Keep it as Mermaid in its own mermaid fence; GitHub draws it. | These kinds used to be on this list and now convert to their own kinds: C4 diagrams, class diagrams, Gantt charts, git graphs, mind maps, pie charts, quadrant charts, Sankey diagrams, timelines, tree views, treemaps, user journeys, and XY charts. A state diagram is now a state diagram and no longer a flow diagram. [Other Mermaid kinds that convert](#other-mermaid-kinds-that-convert) says where each one is explained. ## The migration recipe for an agent Follow these steps in order. Each one is a tool call or a decision. 1. **Convert.** For one diagram, call `convert_mermaid` with the text inside the fence. For a document, call `sync_markdown` with `convertMermaid: true`. 2. **Read every diagnostic.** They are data: `code`, `severity`, `line`, `column`, `message`, and a `hint` that says what to do. Lines and columns point into the Mermaid you sent. A code that starts with `M1` is an error and nothing was converted. A code that starts with `M2` is a warning and the conversion went on. 3. **Resolve each one** with the guidance in [what does not carry over](#what-does-not-carry-over-and-why) and the [diagnostic codes](#diagnostic-codes). Most need no action. Some need a choice, such as which node an edge into a subgraph should reach. 4. **Make it meaningful.** The converter chooses a node type only when the shape or a short label says what the node is. Every other node is a plain `step`. Choose a better type for each (see [how a node type is chosen](#how-a-node-type-is-chosen)), give the diagram a title, and name the groups. Take the icon suggestions in `validation.warnings`: each says which `icon=` to write. Find others with `search_icons` or `map_icons`. 5. **Validate.** Call `draw_diagram` on the edited Plotwork source. It returns every error and warning, and draws nothing until the source is valid. 6. **Render.** Call `draw_diagram` again for the pictures, or put the source in a `diagram` fence and call `sync_markdown`. See [Diagrams in Markdown and on GitHub](https://docs.plotwork.dev/markdown-and-github.md). ### Worked example: one call and its result The call: ```json { "tool": "convert_mermaid", "arguments": { "source": "flowchart TD\n user[User] --> db[(Orders DB)]\n classDef error fill:#f66\n class db error", "render": false } } ``` The `source` argument, written out one line at a time: ```mermaid-source flowchart TD user[User] --> db[(Orders DB)] classDef error fill:#f66 class db error ``` The result, shortened to the fields an agent reads first: ```json { "ok": true, "kind": "flow", "mermaidType": "flowchart", "source": "flow direction=down\n\nnode user person \"User\"\nnode db database \"Orders DB\" tone=red\n\nuser -> db", "diagnostics": [ { "code": "M214", "severity": "warning", "line": 1, "message": "The diagram has no title.", "hint": "Add a title to the header line of the Plotwork source, or a title: line in the Mermaid front matter." }, { "code": "M204", "severity": "warning", "line": 3, "message": "The fill #f66 of the class \"error\" became the tone red.", "hint": "Check that the tone says what it means. The theme draws it in light and dark." } ], "stats": { "nodes": 2, "edges": 1, "groups": 0, "dropped": 0, "renamed": 0 }, "validation": { "valid": true, "errors": [], "warnings": [] }, "outline": [ "Nodes:", "- User, person", "- Orders DB, database", "Connections:", "- User to Orders DB" ], "next": "Read the 2 diagnostics (they point into the Mermaid) and fix what the hints say. Then choose a meaningful type for every node left as step, add a title, and take the icon suggestions in validation.warnings. Call draw_diagram on the edited source to check and draw it, or sync_markdown to place it in a document." } ``` The `source` of the result, written out one line at a time: ```plotwork flow direction=down node user person "User" node db database "Orders DB" tone=red user -> db ``` The field `ok` is true only when `source` passes the Plotwork parser. When it is false, `source` is empty and `diagnostics` holds the reason, so the converter never returns source that would fail validation. ### Worked example: a whole document Ask the agent: "Replace the Mermaid diagrams in README.md." The tool sequence is: | Step | Call | What to do with the result | |---|---|---| | 1 | `sync_markdown` with `markdown` (the file text), `documentPath: "README.md"`, `convertMermaid: true`, `mode: "check"` | A dry run. Read `report`: each Mermaid fence is `converted` or `failed`, with diagnostics. Nothing is rewritten. | | 2 | Fix what the report says, or accept it | Fences that fail are left as Mermaid and reported. | | 3 | `sync_markdown` again with `mode: "sync"` | The result has `markdown` (the rewritten document) and `files` (every SVG, light and dark). | | 4 | Write `markdown` to `README.md` and each `files` entry to its `path` | The tool writes nothing itself; it has no file system. | | 5 | Review the change in git | Read the diff before it is committed. | The result, shortened: ```json { "mode": "sync", "markdown": "<the whole document, rewritten>", "changed": true, "files": [ { "path": "assets/diagrams/readme-sign-in-flow.svg", "content": "<svg text>", "encoding": "utf8" }, { "path": "assets/diagrams/readme-sign-in-flow.dark.svg", "content": "<svg text>", "encoding": "utf8" } ], "report": [ { "id": "readme-sign-in-flow", "source": "mermaid", "kind": "flow", "line": 12, "status": "converted", "diagnostics": [], "message": null } ], "summary": { "diagrams": 1, "synced": 0, "converted": 1, "unchanged": 0, "skipped": 0, "failed": 0, "files": 2 }, "caps": { "hit": false, "messages": [] } } ``` A call handles up to 30 diagrams and a document of up to 10 million characters. If a document has more, `caps` says which fences were skipped; send them in a second call. Nothing is cut silently. ## Concept mapping These tables are the converter's own data, so they cannot drift from what it does. ### Flowchart shapes | Mermaid shape | Example | Plotwork type | Why | |---|---|---|---| | Rectangle | `A[Text]` | `step` | The neutral shape. The label may suggest a type (see the keyword table). | | Rounded rectangle | `A(Text)` | `step` | The same as a rectangle. | | Stadium | `A([Text])` | `terminal` | A start or an end of a flow, which is what a terminal is. | | Subroutine | `A[[Text]]` | `step` | A plain step. Plotwork has no framed box. | | Cylinder | `A[(Text)]` | `database` | A data store. A label such as Redis or Kafka picks the more exact cache or queue. | | Circle | `A((Text))` | `terminal` | Used for connectors and for starts and ends, so a terminal. | | Double circle | `A(((Text)))` | `terminal` | An end of a flow, so a terminal. | | Rhombus | `A{Text}` | `decision` | A question with branches, which is what a decision is. | | Hexagon | `A{{Text}}` | `step` | A plain step. Plotwork has no hexagon. | | Asymmetric | `A>Text]` | `step` | A plain step. | | Parallelogram | `A[/Text/]` | `step` | A plain step. Input and output are not a separate type. | | Parallelogram, leaning left | `A[\Text\]` | `step` | A plain step. | | Trapezoid | `A[/Text\]` | `step` | A plain step. | | Trapezoid, inverted | `A[\Text/]` | `step` | A plain step. | Shapes written in the newer `A@{ shape: name, label: "text" }` form map the same way: | Shape names for `@{ shape: name }` | Plotwork type | Why | |---|---|---| | `lin-cyl`, `disk`, `h-cyl`, `das`, `horizontal-cylinder`, `bow-rect`, `stored-data` | `storage` | Storage that is not a database. | | `cloud` | `external` | Something outside your system, which is what an external node is. | | `doc`, `docs`, `documents`, `lin-doc`, `tag-doc`, `st-doc`, `flag`, `paper-tape` | `step` | A plain step. | | `sm-circ`, `small-circle`, `start` | `terminal` | The start of a flow. | | `fr-circ`, `framed-circle`, `stop` | `terminal` | The end of a flow. | | `st-rect`, `stacked-rectangle`, `lin-rect`, `lin-proc`, `lined-process`, `shaded-process`, `div-rect`, `div-proc`, `divided-process`, `divided-rectangle`, `notch-rect`, `card`, `notched-rectangle`, `sl-rect`, `manual-input`, `win-pane`, `internal-storage`, `curv-trap`, `display`, `delay`, `half-rounded-rectangle`, `tri`, `extract`, `flip-tri`, `manual-file`, `hourglass`, `collate`, `bolt`, `com-link`, `lightning-bolt`, `cross-circ`, `summary`, `f-circ`, `filled-circle`, `junction`, `text`, `brace`, `brace-r`, `braces`, `fork`, `join`, `bang`, `processes`, `procs`, `bow-tie-rectangle` | `step` | A plain step with a warning, since the shape itself is lost. | ### Edges Edge text written as `-->|text|` or `-- text -->` becomes the edge label. A chain such as `A --> B --> C` and a fan such as `A & B --> C & D` become one edge for each pair. An edge animation, `e1@{ animate: true }`, is kept as `animate=on` on the line. The edge id and the speed are dropped and reported, and a link with no arrowhead stays still, because dashes travel toward an arrowhead. | Mermaid link | Plotwork arrow | What changes | |---|---|---| | `A --> B` | `->` | Nothing. | | `A --- B` | `--` | Nothing. | | `A -.-> B` | `-->` | Nothing. | | `A -.- B` | `--` | Plotwork has no dashed line without an arrowhead, so the line is drawn solid. | | `A ==> B` | `->` | Plotwork draws every edge with one stroke weight. | | `A === B` | `--` | Plotwork draws every edge with one stroke weight. | | `A --o B` | `->` | Plotwork has one arrowhead, so a circle or a cross at the end of a link becomes an arrowhead. | | `A --x B` | `->` | Plotwork has one arrowhead, so a circle or a cross at the end of a link becomes an arrowhead. | | `A <--> B` | `<->` | Nothing. | | `A <-.-> B` | `<-->` | Nothing. | | `A <==> B` | `<->` | Plotwork draws every edge with one stroke weight. | | `A o--o B` | `<->` | Plotwork has one arrowhead, so a circle or a cross at the end of a link becomes an arrowhead. | | `A x--x B` | `<->` | Plotwork has one arrowhead, so a circle or a cross at the end of a link becomes an arrowhead. | | `A ~~~ B` | none | Dropped with a warning. It is a layout trick, and layout is done for you. | ### Subgraphs | Mermaid | Plotwork | Note | |---|---|---| | `subgraph id [Title]` | `group id "Title" {`, its nodes, and `}` | The title is the label. A group has no direction of its own. | | `subgraph Title with spaces` | `group Title_with_spaces "Title with spaces" {` | The text is the label, and the id is made valid, with a warning. | | `Nested subgraphs` | Nested groups | Up to 24 levels deep. | | `A node mentioned inside a subgraph` | A node declared inside the group | A node belongs to the first subgraph it is mentioned in. A second subgraph that mentions it gets a warning. | | `A --> subgraphId` | An edge to a node inside the group | Plotwork edges connect nodes. An edge into a subgraph goes to its first node that nothing inside points at; an edge out of one comes from its last node that points at nothing inside. A warning names the node chosen. | | `direction TB inside a subgraph` | Dropped with a warning | Plotwork has one reading direction for the whole diagram. | | `An empty subgraph` | Dropped with a warning | A group with nothing in it draws nothing worth keeping. | | `A subgraph with no end` | Closed at the end of the diagram, with a warning | An extra end is ignored, with a warning. | ### Sequence diagrams Arrows: | Mermaid arrow | Meaning | Plotwork arrow | What changes | |---|---|---|---| | `<<-->>` | Dotted, arrowhead at both ends | `<-->` | Nothing. | | `<<->>` | Solid, arrowhead at both ends | `<->` | Nothing. | | `-->>` | Dotted line with an arrowhead: a reply | `-->` | Nothing. | | `->>` | Solid line with an arrowhead: a call | `->` | Nothing. | | `--x` | Dotted line ending in a cross | `-->` | Plotwork has one arrowhead, so a cross at the end of a message becomes an arrowhead. Say what failed in the label. | | `--)` | Dotted line with an open arrow: asynchronous | `..>` | Plotwork draws an asynchronous message as a dotted line with an arrowhead. | | `-->` | Dotted line with no arrowhead | `--` | Plotwork has no dashed line without an arrowhead, so the line is drawn solid. | | `-x` | Solid line ending in a cross | `->` | Plotwork has one arrowhead, so a cross at the end of a message becomes an arrowhead. Say what failed in the label. | | `-)` | Solid line with an open arrow: asynchronous | `..>` | Plotwork draws an asynchronous message as a dotted line with an arrowhead. | | `->` | Solid line with no arrowhead | `--` | Nothing. | Blocks: | Mermaid block | Plotwork block | Note | |---|---|---| | `loop` | `loop` | The same. | | `alt` | `alt` | The same. | | `opt` | `opt` | The same. | | `par` | `par` | The same. | | `critical` | `alt` | Becomes an alt: the critical region is the first branch and each option is an else branch. | | `break` | `opt` | Becomes an opt, which is the nearest frame for steps that happen only on one path. | | `rect` | none | The coloured background is dropped and the messages inside are kept. | Everything else: | Mermaid | Plotwork | Note | |---|---|---| | `participant A as Alice` | `actor a step "Alice"` | The neutral type step, unless the name says what it is, as Database or Browser do. Choose a type. | | `actor A as Bob` | `actor a person "Bob"` | The stick figure means a person. | | `A messages B without declaring them` | Actors declared in the order they first appear | Plotwork needs every actor declared; the converter adds them. | | `Note over A,B: text` | `note over a, b : "text"` | The same. | | `Note left of A: text, Note right of A: text` | `note over a : "text"` | Plotwork notes sit over a lifeline. A warning says the side was lost. | | `A->>B: text with <br/>` | A message label on one line | Plotwork wraps text itself. | | `A->>A: text` | `a -> a : "text"` | A message to oneself is kept. | | `autonumber` | `numbered=on` on the header | Each message gets a numbered badge. A message before the `autonumber` line, or after `autonumber off`, gets `step=off`. | | `autonumber 10, autonumber 10 5` | `numbered=on start=10`, and `step=15` on a message whose number the count would not give | The numbers Mermaid draws are the numbers Plotwork draws, up to 999. | | `title Text` | The diagram title | A front matter title wins when both are given. | ### Entity relationship diagrams A Mermaid relationship names two entities and no columns. A Plotwork relation joins one column to another, so the converter finds the key columns, and adds a column when it cannot. Each added column is reported. | Meaning | Left marker | Right marker | Plotwork | |---|---|---|---| | Exactly one | `\|\|` | `\|\|` | The key it points at; the foreign key column is not nullable. | | Zero or one | `\|o` | `o\|` | The foreign key column is `nullable`. | | One or more | `}\|` | `\|{` | A foreign key on this side (many to one). The "at least one" minimum is not drawn. | | Zero or more | `}o` | `o{` | A foreign key on this side (many to one). | Whole relationships: | Mermaid | Meaning | Plotwork | |---|---|---| | `A \|\|--o{ B : has` | One A, zero or more B | B holds a foreign key to A. | | `A \|\|--\|\| B : has` | One A, exactly one B | B holds a `unique` foreign key to A, which draws one to one. | | `A }o--o\| B : has` | Many A, zero or one B | A holds a `nullable` foreign key to B. | | `A }o--o{ B : has` | Many to many | A join table with a foreign key to each side. | Lines and keys: | Mermaid line | Meaning | Plotwork | |---|---|---| | `--` | Identifying | A normal relation. Plotwork draws one line style. | | `..` | Non-identifying | A normal relation, with a warning that the dotted line is not drawn. | | Mermaid key | Plotwork flag | Meaning | |---|---|---| | `PK` | `pk` | Primary key | | `FK` | `fk` | Foreign key | | `UK` | `unique` | Unique key | ### State diagrams A state diagram becomes a Plotwork state diagram, which has its own kind: states are rounded cards, transitions are labelled lines, composite states are frames, and the start and end markers, choices, forks, joins and notes are drawn as such. Older versions of the converter turned a state diagram into a flow diagram of groups and `terminal` nodes, so a state diagram that was converted earlier reads differently when it is converted again. See [State diagrams](https://docs.plotwork.dev/state-diagrams.md). | Mermaid | Plotwork | Note | |---|---|---| | `A --> B : event` | A transition `a -> b : "event"` | The label is kept as written, so `event [guard] / action` keeps its guard and its action. | | `state A` | `state a "A"` | A rounded card. Give it a `tone=` for a meaning such as an error or a success. | | `state "Long name" as A` | `state a "Long name"` | The id stays A. | | `A : description` | The state `sub=` text | Each description line stays its own line under the name. | | `[*] --> A` | `start -> a` | The start marker of the scope the line is in: the diagram, or the composite state. | | `A --> [*]` | `a -> end` | The end marker of the scope the line is in. | | `state A { inner states }` | A composite state `state a "A" {` with its states inside | Each composite state has its own start and end markers, and a transition may enter or leave the whole state. | | `--` | `region {` blocks inside the composite state | The states before the first `--` are the first region. A dashed line separates the regions. | | `state A <<choice>>` | `choice a` | A pill with a branch icon. Label the branches with their guards. | | `state A <<fork>> or <<join>>` | `fork a` or `join a` | A bar that splits or merges parallel paths. | | `direction LR` | `direction=right` in the header | Only at the top level. Inside a composite state it is dropped. | | `note right of A : text` | `note a : "text" side=right` | A note beside the state, across the reading direction. A note on a composite state sits inside its frame. | | `classDef, class, :::name, style` | Dropped with a warning | The theme owns colour. A class name such as `error` gives the state the tone red. | ### Other Mermaid kinds that convert More Mermaid kinds convert into a Plotwork kind of their own. Each has a guide that shows what is kept and what is dropped. The codes in the last column are the ones that only that kind raises, and the [diagnostic codes](#diagnostic-codes) table explains every code. | Mermaid | Plotwork kind | Guide | Codes of its own | |---|---|---|---| | `classDiagram` | `class` | [Class diagrams](https://docs.plotwork.dev/class-diagrams.md) | `M240`, `M241`, `M242` | | `gantt` | `gantt` | [Gantt charts](https://docs.plotwork.dev/gantt-charts.md) | `M130`, `M131`, `M132`, `M230` to `M238` | | `gitGraph` | `git` | [Git graphs](https://docs.plotwork.dev/git-graphs.md) | `M120`, `M220`, `M221`, `M222` | | `timeline` | `timeline` | [Timelines](https://docs.plotwork.dev/timelines.md) | `M260` | | `journey` | `journey` | [User journeys](https://docs.plotwork.dev/user-journeys.md) | `M270`, `M271` | | `mindmap` | `mindmap` | [Mind maps](https://docs.plotwork.dev/mind-maps.md) | `M180`, `M280`, `M281`, `M282` | | `stateDiagram` | `state` | [State diagrams](https://docs.plotwork.dev/state-diagrams.md) | `M250`, `M251` | | `treemap-beta`, `treemap` | `treemap` | [Treemaps](https://docs.plotwork.dev/treemaps.md) | None of its own | | `pie` | `pie` | [Pie charts](https://docs.plotwork.dev/pie-charts.md) | None of its own | | `quadrantChart` | `quadrant` | [Quadrant charts](https://docs.plotwork.dev/quadrant-charts.md) | None of its own | | `C4Context`, `C4Container`, `C4Component`, `C4Dynamic`, `C4Deployment` | `c4` | [C4 diagrams](https://docs.plotwork.dev/c4-diagrams.md) | None of its own | | `sankey-beta`, `sankey` | `sankey` | [Sankey diagrams](https://docs.plotwork.dev/sankey-diagrams.md) | None of its own | | `xychart-beta`, `xychart` | `xychart` | [XY charts](https://docs.plotwork.dev/xy-charts.md) | None of its own | | `treeView-beta`, `treeView` | `tree` | [Tree diagrams](https://docs.plotwork.dev/tree-diagrams.md) | None of its own | Three kinds have no Mermaid type to read: [Layered architecture](https://docs.plotwork.dev/layered-architecture.md), [Matrices](https://docs.plotwork.dev/matrices.md), and [Swimlane diagrams](https://docs.plotwork.dev/swimlane-diagrams.md). Each guide says how to move a Mermaid diagram or a Markdown table to it by hand. ## How a node type is chosen The converter is conservative. It picks a type in this order and stops at the first that applies: 1. The construct itself decides, as for a flowchart rhombus (a `decision`) or a stadium (a `terminal`). 2. A class name that carries meaning, such as `database`, `queue`, `external`, or `error`. The converter reports that it used the class. 3. The shape: a cylinder is a `database`, a rhombus is a `decision`, a stadium or a circle is a `terminal`, a cloud is `external`. A label keyword may refine a cylinder to a cache, a queue, or storage. 4. A keyword in a short label of at most three words, and only when exactly one type matches. "Orders DB" is a `database`; "Redis queue" matches two types and stays neutral; a sentence is never read. 5. Otherwise the neutral `step`, which the converter lists in one warning so you choose a better one. It writes an icon only when the label names a brand. "PostgreSQL", "Redis cache", or a second line such as "Stripe API" gets that brand's mark (`icon=simple:postgresql`). A decision, a start or end, and a plain `step` never get one, and a name that is only a word for a kind of thing ("Message Queue") keeps the icon of its type. The Plotwork validator suggests the other icons from the labels, and those suggestions come back in `validation.warnings`. An HTTP status code at the start of a label sets a tone when the node has none: `200` and the other 2xx codes are green, and 4xx and 5xx codes are red. "Return 429 Too Many Requests" is red. A 3xx code gets no tone, because a redirect is neither good nor bad. The words it looks for: | Type | Whole label | Last word of a short label | Product names in a short label | |---|---|---|---| | `database` | `database`, `databases`, `db`, `datastore`, `data store`, `sql` | `database`, `db`, `datastore` | `postgres`, `postgresql`, `mysql`, `mariadb`, `sqlite`, `mongodb`, `mongo`, `dynamodb`, `cassandra`, `cockroachdb`, `oracle`, `mssql` | | `cache` | `cache`, `caches` | `cache` | `redis`, `memcached`, `valkey` | | `queue` | `queue`, `queues`, `topic`, `message bus`, `event bus` | `queue`, `topic`, `bus` | `kafka`, `rabbitmq`, `sqs`, `nats`, `pubsub`, `kinesis` | | `storage` | `storage`, `bucket`, `buckets`, `blob storage`, `file storage` | `bucket`, `storage` | `s3`, `gcs` | | `person` | `user`, `users`, `customer`, `customers`, `admin`, `administrator`, `developer`, `visitor`, `end user`, `operator`, `person`, `user request` | none | none | | `client` | `browser`, `client`, `clients`, `frontend`, `front end`, `web app`, `mobile app`, `ui`, `cli`, `spa`, `webapp`, `client request` | `browser` | none | | `gateway` | `gateway`, `api gateway`, `load balancer`, `reverse proxy`, `proxy`, `cdn`, `ingress` | `gateway`, `balancer`, `proxy` | `nginx`, `envoy`, `traefik`, `haproxy`, `kong` | | `service` | `api`, `service`, `backend`, `back end`, `server`, `microservice`, `rest api`, `web server`, `app server` | `service`, `api`, `server`, `microservice`, `backend` | none | | `function` | `worker`, `workers`, `worker pool`, `cron`, `cron job`, `scheduler`, `lambda`, `function`, `job`, `batch job` | `worker`, `cron`, `scheduler`, `lambda`, `function` | `lambda` | | `external` | `external`, `external system`, `third party`, `3rd party`, `external api`, `partner`, `payment provider`, `email provider`, `sms provider`, `payment processor` | none | `stripe`, `sendgrid`, `twilio`, `paypal`, `mailgun`, `braintree` | | `identity` | `idp`, `identity provider`, `sso`, `auth server`, `authorization server`, `oauth`, `oidc` | none | `okta`, `auth0`, `keycloak` | | `secret` | `secret`, `secrets`, `vault`, `key vault`, `kms`, `secret store` | none | none | Class names that give a type or a tone: | Class names | Becomes | |---|---| | `database`, `db`, `datastore`, `store` | type `database` | | `cache` | type `cache` | | `queue`, `topic`, `broker` | type `queue` | | `storage`, `bucket` | type `storage` | | `external`, `thirdparty`, `ext`, `vendor` | type `external` | | `user`, `person`, `actor` | type `person` | | `client`, `browser`, `frontend` | type `client` | | `service`, `api`, `backend` | type `service` | | `gateway`, `proxy` | type `gateway` | | `worker`, `job`, `function`, `lambda` | type `function` | | `decision` | type `decision` | | `start`, `end`, `terminal` | type `terminal` | | `error`, `danger`, `fail`, `failure`, `critical` | tone `red` | | `warning`, `warn`, `caution` | tone `amber` | | `success`, `ok`, `done`, `pass`, `passed` | tone `green` | ### Fills become tones A `style` line, a `classDef` line, and a `class` or `:::name` that points at one can set a fill. The converter reads the fill and keeps it as the tone of the node (or the group) when the colour is clear: it works out the hue of the colour and takes the tone whose band holds it. Each one is reported with `M204`, the first few on their own and the rest in one summary, so a diagram painted by an agent is not buried in warnings. A tone is a meaning, and the theme draws it in light and dark. | Hue of the fill | Tone | Colours that land there | |---|---|---| | 345 to 15 degrees | `red` | red, crimson, tomato | | 15 to 38 degrees | `orange` | orange, salmon | | 38 to 68 degrees | `amber` | yellow, gold, khaki | | 68 to 160 degrees | `green` | lime, green, light green | | 160 to 200 degrees | `aqua` | teal, cyan, turquoise | | 200 to 255 degrees | `blue` | sky blue, blue, navy, a periwinkle such as #bbf | | 255 to 320 degrees | `violet` | purple, violet, indigo, lavender, a pastel such as #f9f | These fills become no tone, and are reported as dropped styling with `M200` along with the strokes, text colours, and widths: | Fill | Why it becomes no tone | |---|---| | Greys, such as #ddd and #333 | A grey is the page's own neutral, so there is nothing to keep. | | Near white and near black, such as #fff and #111 | Neither says a hue; they are the surface and the ink. | | Pink and magenta, from 320 to 345 degrees | Plotwork has no pink, and the nearest tone would be a guess. | | A fill that is mostly transparent, or a colour that cannot be read | It is not a fill an agent chose to show. | A fill set on the node itself wins over a tone a class name such as `error` suggests. ### Edge labels that get a status icon An edge label that states an outcome gets a status icon, written as `icon=status:name` on the line. The icon brings its own colour, so a branch such as "Rate limited" says what happened without a colour code to learn. A label matches only when all of it is one of the phrases below, in any case and with its punctuation ignored, so "Sends the order" and "Webhook" are never given an outcome they do not state. The labels `yes`, `no`, `true`, and `false` get their tick and cross from the language itself, so the converter writes nothing for them. | Status icon | Edge labels that get it | |---|---| | `status:yes` | `y`, `ok`, `okay`, `pass`, `passed`, `valid`, `correct` | | `status:no` | `n`, `invalid`, `incorrect` | | `status:success` | `success`, `successful`, `succeeded`, `done`, `complete`, `completed`, `approved`, `accepted` | | `status:error` | `error`, `errors`, `failed`, `failure`, `fail`, `rejected`, `exception`, `crash`, `crashed` | | `status:verified` | `authenticated`, `verified`, `signed in`, `logged in` | | `status:allowed` | `allowed`, `authorized`, `authorised`, `permitted`, `granted`, `access granted` | | `status:denied` | `denied`, `forbidden`, `unauthorized`, `unauthorised`, `unauthenticated`, `not authenticated`, `not authorized`, `not allowed`, `access denied`, `permission denied` | | `status:blocked` | `blocked`, `banned`, `throttled`, `rate limited`, `rate limit exceeded`, `too many requests` | | `status:warning` | `warning`, `warn`, `degraded` | | `status:expired` | `expired`, `timeout`, `timed out` | | `status:retry` | `retry`, `retrying`, `try again` | | `status:pending` | `pending`, `waiting` | | `status:skipped` | `skip`, `skipped` | | `status:running` | `running`, `in progress`, `processing` | | `status:revoked` | `revoked` | ## What does not carry over and why Plotwork owns colour, spacing, and layout, so a diagram cannot set them. The rule for every row is the same: choose a node type or a tone for meaning, and let the theme choose the colour. | Mermaid | Applies to | Why it does not carry over | What to do instead | |---|---|---|---| | `classDef` | flowchart, state | The theme owns every colour, in light and dark, so a diagram cannot set one. | Choose a node type or a tone for what the node means; the theme chooses the colour. | | `class` and `:::name` | flowchart, state | The theme owns every colour, in light and dark, so a diagram cannot set one. | Choose a node type or a tone for what the node means; the theme chooses the colour. | | `style` | flowchart, state | The theme owns every colour, in light and dark, so a diagram cannot set one. | Choose a node type or a tone for what the node means; the theme chooses the colour. | | What a `style` or `classDef` line sets besides a fill that has a tone | flowchart | The theme owns every colour, in light and dark, so a diagram cannot set one. A fill is kept as the nearest tone; strokes, text colours, widths, and a fill with no tone are left out. | Choose a node type or a tone for what the node means; the theme chooses the colour. | | `linkStyle` | flowchart | Plotwork draws every edge the same way. | Use an arrow form for meaning: `->` for a call, `-->` for a reply or optional link, `..>` for an asynchronous call. | | `%%{init}%%` directives, themes, `themeVariables`, `curve` | all | Theme and layout settings belong to Plotwork, not to a diagram. | Remove it. Use `direction=` in the header for the reading direction. | | Front matter `config:` | all | Theme and layout settings belong to Plotwork, not to a diagram. | Remove it. A `title:` is kept as the diagram title. | | `click` callbacks and links | flowchart, state | A Plotwork diagram is a static picture with no script and no links inside it. | Link to the page in the surrounding text, or put the name of the target in the node `sub` text. | | Tooltips | flowchart | A static picture has no hover. | Put the detail in the node `sub` text or in the diagram `desc=`. | | Actor `link` and `links` | sequence | A static picture has no menus or links. | Link to the page in the surrounding text. | | `direction` inside a subgraph | flowchart | Plotwork has one reading direction for the whole diagram. | Set `direction=` on the header, or split the subgraph into its own diagram. | | Extra dashes in a link, as in `---->` | flowchart | Layout places nodes; an author cannot ask for longer edges. | Nothing: the layout spaces nodes for you. | | Invisible link `~~~` | flowchart | It is a trick to move nodes, and layout does that for you. | Group the nodes with a `group` if they belong together. | | Edge ids | flowchart | Plotwork names an edge by the two nodes it joins, so an id has nothing to refer to. | Nothing. An id that only animates the edge is not lost: `e1@{ animate: true }` becomes `animate=on` on the line. | | Animation speeds `fast` and `slow` | flowchart | Plotwork animates every line at one calm pace, so a speed has nothing to set. | The line is still animated with `animate=on`. Say in the label if a flow is meant to be fast. | | `curve` on an edge | flowchart | Layout routes every edge, with square bends and rounded corners, so a curve cannot be chosen. | Nothing: layout draws the edge for you. | | Activations `+`, `-`, `activate`, `deactivate` | sequence | Plotwork draws lifelines without activation bars. | Say who is busy in the message labels or a `note over`. | | `create` and `destroy` | sequence | Every Plotwork participant has a lifeline for the whole diagram. | Declare the participant at the top and say it is created or destroyed in a `note over`. | | `box` around participants | sequence | Plotwork has no frame around participants. | Put the grouping in the participant `sub` text, such as the name of the system. | | `rect` backgrounds | sequence | The theme owns every colour, in light and dark, so a diagram cannot set one. | The messages inside are kept. Use `opt` or `loop` if the highlighted part means something. | | State `classDef`, `class`, and `style` | state | The theme owns every colour, in light and dark, so a diagram cannot set one. | A `tone=` on a state is a meaning, not a colour. A class name such as `error` or `success` gives the state that tone, and the class itself is dropped. | | Entity `style` | ER | The theme owns every colour, in light and dark, so a diagram cannot set one. | A `tone=` on a table is a meaning, not a colour. | | Non-identifying relationships `..` | ER | Plotwork draws one line style for a relation. | Mark an optional reference with `nullable` on the foreign key column. | | "One or more" on the many side | ER | A relation draws many to one; a minimum count is a rule, not a shape. | Say it in the relation label if it matters. | Other text is cleaned and the change is reported: HTML tags and Markdown marks are removed, `<br/>` and `\n` become a line break (a second line of a node label becomes its `sub=` text), entities such as `#quot;` and `&` are decoded, and Font Awesome tokens such as `fa:fa-database` are removed with the closest bundled icon named in the hint. Quotes and backslashes are escaped so every label keeps its exact text. Ids that are not valid Plotwork ids (spaces, dots, accents, a leading digit) are renamed the same way every time, and each rename is reported. The label keeps the original text. ## Diagnostic codes | Code | Severity | What it means | What to do | |---|---|---|---| | `M100` | error | The text is empty or does not start with a Mermaid diagram type. | Check that the first line is a diagram type such as `flowchart TD` or `sequenceDiagram`. | | `M101` | error | Plotwork does not draw this kind of Mermaid diagram. | Keep it as Mermaid in its own fence, or redraw it as the closest Plotwork kind, which the hint names. | | `M102` | error | The diagram is over a size limit: too much text, too many nodes or edges, or nesting that is too deep. | Split it into several diagrams along a subgraph, then convert each. | | `M103` | error | The diagram has a header but nothing to draw. | Add nodes or messages to the Mermaid, or delete the fence. | | `M104` | error | The converter produced Plotwork that did not pass validation, so it returned nothing. | Report the Mermaid that caused it. Redraw the diagram by hand in the meantime. | | `M110` | error | A statement could not be read as Mermaid, so nothing was converted. | Fix the line the diagnostic points at, or convert with `lenient` to skip unreadable lines and keep the rest. | | `M200` | warning | Strokes, text colours, classes, themes, or other styling were dropped. A fill that has a tone is not here: it is kept as a tone (M204). | Choose a node type or a tone for what a node means. The theme chooses the colour. | | `M201` | warning | A click action, link, or tooltip was dropped. | Link to the page in the surrounding text. A diagram is a static picture. | | `M202` | warning | A hint about layout, such as a link length or a direction inside a subgraph, was dropped. | Nothing. Plotwork places nodes itself. Set `direction=` on the header if the reading direction matters. | | `M203` | warning | Something that carried meaning has no Plotwork form and was dropped, such as activations or state notes. | Say it in a label, a `sub=` text, or a `note over` if the reader needs it. | | `M204` | warning | A construct was replaced by the nearest Plotwork one, such as a cross end becoming an arrowhead or a fill becoming the nearest tone. | Read the message. Edit the Plotwork if the nearest form says the wrong thing. | | `M205` | warning | Label text was cleaned: HTML tags, Markdown marks, or line breaks were removed or moved. | Compare the label in the Plotwork with the Mermaid. A second line became the `sub=` text of a node. | | `M206` | warning | An id was not a valid Plotwork id and was renamed. | Nothing, unless you refer to the id elsewhere. The label keeps the original text. | | `M207` | warning | A node type or tone was taken from a class name such as `database` or `error`. | Check that the type is right. The class itself was dropped. | | `M208` | warning | Nodes got the neutral type `step` because nothing in the Mermaid says what they are. | Choose a type for each node that means something: `database`, `service`, `queue`, `person`, and so on. | | `M209` | warning | An edge repeated an earlier one exactly and was dropped. | Nothing. Keep both only if they mean different things, and then give them different labels. | | `M210` | warning | An edge pointed at a subgraph, and Plotwork edges connect nodes, so it was redrawn to a node inside. | Check that the node named in the message is the one the edge means. | | `M211` | warning | A relationship names no columns, and Plotwork draws a relation between two columns, so one was added. | Rename the added column, or replace it with the real foreign key. | | `M212` | warning | A many-to-many relationship was written as a join table with a foreign key to each side. | Rename the join table, and add the columns it holds. | | `M213` | warning | A node appeared in more than one subgraph, and Plotwork puts a node in one group. | Check that the group the message names is the one you want. | | `M214` | warning | The diagram has no title. A title names the picture, its file, and its alt text. | Add a title to the header line of the Plotwork source. | | `M215` | warning | A Font Awesome icon in a label was removed. Plotwork icons are a closed set. | Set `icon=` on the node. The hint names the closest bundled icon. | | `M216` | warning | An empty subgraph, state, or entity was dropped, or a label was empty and the id was used. | Add content to it in the Mermaid first if it should be drawn. | | `M217` | warning | A subgraph or block was never closed, or an `end` had nothing to close, and the converter repaired it. | Check where the block should end. | | `M218` | warning | A statement could not be read and was skipped because the conversion is lenient. | Fix the Mermaid line, or add the edge or node by hand in the Plotwork source. | | `M250` | warning | A note on a composite state was drawn inside its frame, below the states it holds, because the frame has no free side. | Check that the note still reads well there. A note on a simple state keeps its side. | | `M251` | warning | A state was named `start` or `end`, which Plotwork keeps for the start and end markers, so it was renamed. | Nothing, unless you refer to the id elsewhere. The label keeps the original name. | | `M270` | warning | A journey task had a score outside 1 to 5, or a fraction, and Plotwork keeps whole scores from 1 to 5, so it was clamped or rounded. | Check that the task still reads as you meant. 1 is frustrated and 5 is delighted. | | `M271` | warning | A journey task named an empty actor, or the same actor in two spellings that differ only in case or spacing, so the empty name was dropped or the spellings were merged. | Nothing, unless the two names were meant to be different people. Give them different words. | | `M260` | warning | A timeline line had a # that is not part of an entity, and Mermaid reads everything after it as a comment, so the converter cut the line there as well. | Check that the text before the # says what you meant. Write the # as the entity `#35;` in the Mermaid if it was part of the text. | | `M120` | error | A gitGraph statement names a branch or commit that does not exist, makes a branch that already exists, merges a branch into itself, or branches before the first commit, so the history cannot be replayed. | Fix the line the diagnostic points at, or convert with `lenient` to skip statements that cannot be replayed. | | `M220` | warning | Mermaid draws a generated hash on a commit that has no id, and Plotwork draws a commit with no message as a bare dot. | Give the commit an `id:` in the Mermaid if the picture should say something next to it. | | `M221` | warning | A `cherry-pick` named a `parent:`, and Plotwork has no choice of parent: a cherry-pick copies one commit. | Nothing, unless the picked commit is a merge and the parent mattered. Say which side in the commit message. | | `M222` | warning | A commit had both an `id:` and a `msg:`, and Mermaid draws only the id, so the message was dropped. | Put the text the picture should show in the `id:`, or in the message of the Plotwork commit. | | `M130` | error | A Gantt chart is scheduled in hours or minutes, or its `dateFormat` holds a time of day. Plotwork schedules in whole days, so nothing was converted. | Redraw the plan in days, or keep the chart as Mermaid in its own mermaid fence. | | `M131` | error | A Gantt task comes after, or ends where, a task that is not in the chart, or waits for itself in a circle, so its dates cannot be worked out. | Fix the id the diagnostic names, or give the task a start date. Convert with `lenient` to skip tasks that cannot be placed. | | `M132` | error | A Gantt `dateFormat` uses a token Plotwork does not read, such as a month name, so the dates cannot be read. | Write the dates as YYYY-MM-DD, DD/MM/YYYY, or another format of a year, a month, and a day in numbers. | | `M230` | warning | A Gantt `axisFormat` was dropped. Plotwork writes the labels of the axis itself, from the step it draws. | Set `unit=` on the header to choose the step of the axis: day, week, month, quarter, or year. | | `M231` | warning | A Gantt `excludes` or `includes` has no Plotwork form, apart from weekends and single days, which become `weekends=skip` and `holidays=`. | Use `weekends=skip` and `holidays=` on the header, and check that the lengths still add up. | | `M232` | warning | A Gantt `tickInterval` was dropped because it is not one day, week, or month, which Plotwork writes as `unit=`. | Set `unit=` on the header, or leave it out so the step follows the span of the plan. | | `M233` | warning | A Gantt `todayMarker` was dropped. Mermaid marks the day the chart is drawn, and Plotwork has no clock. | Add `today=YYYY-MM-DD` to the header to mark a day, and move it as the plan does. | | `M234` | warning | A Gantt milestone had a length, and Mermaid draws it in the middle of it, so it sits on one day in Plotwork. | Check that the day is the one you mean, and set `at=` to change it. | | `M235` | warning | A Gantt length changed: a fraction was rounded, a length of zero became one day, or a week was written in days because Mermaid counts it as seven working days. | Check the length in the Plotwork source, and write it as a whole number of days, weeks, or months. | | `M236` | warning | A Gantt task tag such as `vert` has no Plotwork form and was dropped. The tags `done`, `active`, `crit`, and `milestone` are kept. | Use a milestone for a point in time, or `today=` for the current day. | | `M237` | warning | A Gantt task ended where another task starts (`until`), and is written with the day that was, so it no longer moves with that task. | Check the date. Use `after=` on the other task if it should follow this one instead. | | `M238` | warning | A Gantt layout setting (`topAxis`, `weekday`, `weekend`, `displayMode`) was dropped. Plotwork chooses the layout. | Nothing: the axis is above the chart, the week starts on Monday, and weekends are Saturday and Sunday. | | `M240` | warning | A class member was written type first (`String name`, `getName() String`, `List~int~ getIds()`), and Plotwork writes a member name first, with the type after a colon, so it was turned round. | Check that the member reads as you meant. When neither word reads as a type, or both do, the text is kept as it was. | | `M241` | warning | A class relation has no exact Plotwork form (a lollipop interface, a dashed line with no marker, or a marker at both ends), so the nearest of the eight relations was drawn. | Read the message for what was drawn. Edit the Plotwork if the nearest relation says the wrong thing. | | `M242` | warning | A class had more than one annotation (`<<interface>>`, `<<Service>>`), and a Plotwork class has a type and one stereotype word, so the extra one was dropped. | Keep the annotation that matters, or say the other in a note on the class. | | `M180` | error | A mind map has one root, and a later line is indented as far as it, or less, so it has no parent. Nothing was converted. | Indent the line under the node it belongs to, or move it into its own mindmap block. | | `M280` | warning | A mind map node shape has no exact Plotwork form, such as a bang or a cloud, so the nearest shape was drawn. | Read the message for the shape that was drawn. Set `shape=` on the topic if another one reads better. | | `M281` | warning | A mind map `::icon()` line was dropped. Plotwork icons are a closed set. | Set `icon=` on the topic. The hint names the closest bundled icon. | | `M282` | warning | A mind map node had text before its brackets. Mermaid reads that as an id and does not draw it, so only the text inside the brackets was kept. | Put all of the label inside the brackets if the text before them belongs in it. | Diagnostics from the converter point into the Mermaid. The Plotwork validator's own warnings, such as an icon suggestion, point into the converted source, and they come back separately in `validation`. ## Before and after Each example shows the Mermaid source, the picture Mermaid draws from it, the Plotwork the converter produced, the picture Plotwork draws from that, and a table of what differs, with nothing edited by hand. After the conversion, an agent would still read the diagnostics, choose types for the nodes left as `step`, and take the icon suggestions. The Mermaid pictures were drawn with Mermaid 12.1.0, in its default and dark themes, with Arial text. They are fixed pictures, drawn once from the source above them. They are not drawn by this page. Some differences hold for every diagram: | What | Mermaid | Plotwork | |---|---|---| | Meaning | The shape of a node, and a style you write for each color. | The type of a node, which sets its icon, tone, and shape. A color is never written. | | Text for a reader who cannot see it | A role and the kind of diagram. A title and a description only when the source has `accTitle` and `accDescr` lines. | A title, a description, an outline of the nodes and connections, and the source the picture was drawn from, in every picture. | | Text width | Measured in a browser when the diagram is drawn, so one source can give boxes of different sizes on different machines. | Measured with font metrics built into the renderer, so one source gives the same picture on every machine. | | Light and dark | A theme chosen when the diagram is drawn. | A light picture and a dark picture of every diagram, chosen by the page. ### A flowchart with a subgraph and a decision Mermaid: ```mermaid-source --- title: Sign-in flow --- flowchart TD user[User] --> gw[API gateway] gw --> auth{Credentials valid?} auth -->|yes| session[Create session] auth -->|no| denied([Access denied]) subgraph store [Data] db[(Users DB)] audit[Audit log] end auth -.-> db session --> audit classDef database fill:#9cf class audit database ``` Mermaid draws it like this: *Picture: How Mermaid draws the sign-in flow: a title, boxes joined by arrows, a diamond for the decision, a rounded end, a cylinder for the database, and a filled box around the Data group.* The converter reports the `database` type it took from the class of that name for the audit log, the class's blue fill kept as the tone blue, and the one node left as a plain `step`. The Plotwork: **Diagram source** ```diagram flow "Sign-in flow" direction=down node user person "User" node gw gateway "API gateway" node auth decision "Credentials valid?" node session step "Create session" node denied terminal "Access denied" group store "Data" { node db database "Users DB" node audit database "Audit log" tone=blue } user -> gw gw -> auth auth -> session : "yes" auth -> denied : "no" auth --> db session -> audit ``` *Picture: Sign-in flow: 7 nodes, 6 connections.* **Text description** **Nodes:** - User, person - API gateway, gateway - Credentials valid?, decision - Create session, step - Access denied, terminal - Data tier: Data - Users DB, database - Audit log, database **Connections:** - User to API gateway - API gateway to Credentials valid? - Credentials valid? to Create session: yes - Credentials valid? to Access denied: no - Credentials valid? to Users DB (dashed) - Create session to Audit log What differs between the two pictures: | What | Mermaid | Plotwork | |---|---|---| | Title | Drawn above the diagram, from the front matter. | Not drawn. It names the picture for a screen reader. | | What a node is | The shape: a box, a diamond for the decision, a rounded end, a cylinder for the database. Every shape has the same lavender fill unless a style says otherwise. | The type: an icon and a tone for each node, such as a person, a gateway, an amber decision, and a database. The end is a pill. | | Color | `classDef database fill:#9cf` paints Audit log blue, and the subgraph has a pale yellow fill. | No color is written in the source. Audit log takes the type `database` from the class name, and the class's fill becomes `tone=blue`, which the theme draws in light and dark. | | Edge labels | The words `yes` and `no` on the lines. | The same words, with a green tick and a red cross added, because those labels get a status icon. | | The dotted link | A dotted line (`-.->`). | A dashed line (`-->`), which is the line for a link that is optional or indirect. | | The group | A filled box with its name centered at the top. | A frame with a corner tab that holds the name. | ### A sequence diagram with an alt block Mermaid: ```mermaid-source --- title: Token refresh --- sequenceDiagram autonumber actor U as User participant A as App participant I as Identity provider U->>A: Open the app A->>+I: Refresh token alt token valid I-->>-A: New access token else token revoked I-->>A: 401 sign in again A->>U: Ask to sign in end Note over A,I: Tokens last 15 minutes ``` Mermaid draws it like this: *Picture: How Mermaid draws the token refresh: a title, a stick figure and two boxes at the top and again at the bottom, numbered messages, an activation bar, a dotted alt frame, and a yellow note.* The converter turns `autonumber` into `numbered=on` on the header, reports the dropped activation marks, and reports the participant `App` left as a plain `step`. The `actor` became a `person`, and the name Identity provider became an `identity`. The Plotwork: **Diagram source** ```diagram sequence "Token refresh" numbered=on actor U person "User" actor A step "App" actor I identity "Identity provider" U -> A : "Open the app" A -> I : "Refresh token" alt "token valid" { I --> A : "New access token" } else "token revoked" { I --> A : "401 sign in again" A -> U : "Ask to sign in" } note over A, I : "Tokens last 15 minutes" ``` *Picture: Token refresh: 3 participants.* **Text description** **Participants:** - User, person - App, step - Identity provider, identity **Steps:** - 1. User to App: Open the app - 2. App to Identity provider: Refresh token - alt: token valid - 3. Identity provider to App (dashed): New access token - else: token revoked - 4. Identity provider to App (dashed): 401 sign in again - 5. App to User: Ask to sign in - Note over App and Identity provider: Tokens last 15 minutes What differs between the two pictures: | What | Mermaid | Plotwork | |---|---|---| | Participants | A stick figure for the actor and a box for each participant, drawn at the top and again at the bottom. | A card for each, at the top only, with an icon from its type: a person, and an identity provider. App stays a plain card, because its type is `step`. | | Numbers and activation | `autonumber` numbers each message, and `activate` draws a bar on the Identity provider. | The numbers carry over as `numbered=on`, so each message gets the same numbered badge. The activation bar is dropped and reported. | | The alt block | A dotted frame with an `alt` tab. The conditions sit in brackets at the center, over the lifeline of App. | A rounded frame with an `alt` chip and an `else` chip, each beside its condition, at the left. | | Replies | Dashed lines with filled arrowheads. | Dashed lines with open arrowheads. | | The note | A yellow box. | A tinted box with an accent bar. | ### An entity relationship diagram Mermaid: ```mermaid-source --- title: Orders --- erDiagram CUSTOMER ||--o{ ORDER : places ORDER ||--|{ LINE_ITEM : contains CUSTOMER { int id PK string email UK string name } ORDER { int id PK int customer_id FK date placed_at } LINE_ITEM { int order_id FK string sku int quantity } ``` Mermaid draws it like this: *Picture: How Mermaid draws the orders: a title and three tables of type, name, and key columns, stacked in one column and joined by crow's foot lines with a label on each.* The many side of each relationship holds the foreign key, and the converter matched each one to a column that was already declared as `FK`, so it added none. It reports that the "one or more" minimum on `contains` is not drawn. The Plotwork: **Diagram source** ```diagram schema "Orders" table CUSTOMER { id int pk email string unique name string } table ORDER { id int pk customer_id int fk placed_at date } table LINE_ITEM { order_id int fk sku string quantity int } ORDER.customer_id -> CUSTOMER.id : "places" LINE_ITEM.order_id -> ORDER.id : "contains" ``` *Picture: Orders: 3 tables, 2 relations.* **Text description** **Tables:** - CUSTOMER - id int (primary key) - email string (unique) - name string - ORDER - id int (primary key) - customer_id int (foreign key) - placed_at date - LINE_ITEM - order_id int (foreign key) - sku string - quantity int **Relations:** - ORDER.customer_id references CUSTOMER.id (many to one): places - LINE_ITEM.order_id references ORDER.id (many to one): contains What differs between the two pictures: | What | Mermaid | Plotwork | |---|---|---| | A table | Three columns: the type, the name, and the key. | A card with an icon in its header. The key is a badge before the name, and the type sits at the right edge. | | The unique key | Written `UK` in the source and drawn `UK`. | The converter turns `UK` into the flag `unique`, which is drawn as `UQ`. | | The ends of a relationship | Each end says its minimum and its maximum: a double bar for exactly one, a circle and a crow's foot for zero or more. | A tick for the one side and a crow's foot for the many side. The "one or more" minimum on `contains` is not drawn, and the converter reports that. | | The label | In a box on the line. | In a pill on the line. | | Layout | The three tables stack in one column. | The three tables sit in a row. | ## Converting a documentation folder safely Go one file at a time, and read before you write. Ask your agent to run the steps, or make the calls yourself. | Step | Call | Why | |---|---|---| | 1. Dry run | `sync_markdown` with the document, its path, `convertMermaid: true`, and `mode: "check"` | Reports each Mermaid fence and changes nothing. Repeat for every Markdown file. | | 2. Read the report | | Each fence has a status and its diagnostics. A `failed` fence could not be converted and is left exactly as it was. | | 3. Fix or accept | | Fences that fail stay as Mermaid. To convert a diagram that has a line that cannot be read, call `convert_mermaid` on it with `lenient: true`, which skips the line with a warning. | | 4. Write | `sync_markdown` again, with `mode: "sync"` | Replaces each convertible fence with a `diagram` fence and returns the picture files and blocks. Everything else in the file is unchanged, byte for byte. Save the `markdown` over the file and each entry of `files` at its path. | | 5. Review | `git diff` | Read every changed diagram before it is committed. | To convert a whole folder, ask the agent to call `sync_markdown` on each file. Keep the dry run first for every file. For a single diagram, call `convert_mermaid` with the Mermaid text. It returns the Plotwork source and the diagnostics as data, and you decide where to save the source. ## FAQ | Question | Answer | |---|---| | Does it handle my theme and colours? | Only as meaning. A class or style fill becomes the nearest tone, reported with `M204`, and the theme draws that tone in light and dark. Themes, strokes, text colours, and a fill with no tone are dropped with a diagnostic. | | What about links and click actions? | Dropped with a diagnostic. A diagram is a static picture. Link to the page in the text around it. | | Can I convert a whole repository? | Yes. Ask the agent to call `sync_markdown` on each Markdown file. Dry run first. | | How do I review the result? | Read the diagnostics, then the outline (`outline` in the result), then the picture, then the git diff. | | Why is a node a plain step? | Nothing in the Mermaid says what it is. Choose a type: database, service, queue, person, gateway, function, external. | | Why did an edge move? | Plotwork edges connect nodes, so an edge into a subgraph goes to a node inside it. A diagnostic names the node. | | What if one line cannot be read? | By default nothing is converted, so a diagram never loses a line unnoticed. The error names the line. Set `lenient: true` on `convert_mermaid` to skip such lines with a warning and convert the rest, then add what was skipped by hand. | | Can I keep some diagrams as Mermaid? | Yes. A fence that is not converted stays as it was, and GitHub keeps drawing it. | | Does the converter call a model or the network? | No. It is plain code. It does not fetch URLs, read files, or run anything from the diagram. | | What are the size limits? | 50,000 characters, 300 nodes, 600 edges, and 24 levels of nesting for one diagram. Larger input comes back as an error that says to split it. | ## See also | Doc | Why | |---|---| | [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md) | The Plotwork language by example | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The validate and fix loop, and the one-call workflows | | [Diagrams in Markdown and on GitHub](https://docs.plotwork.dev/markdown-and-github.md) | How a fence becomes a picture | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | `convert_mermaid`, `sync_markdown`, and the other tools | | [Language reference](https://docs.plotwork.dev/reference/language.md) | Every word the language accepts, and every diagnostic code | --- # Importing from code and data > Page: https://docs.plotwork.dev/importing-from-code-and-data. Index of every page: https://docs.plotwork.dev/llms.txt `import_source` turns a file you already have into a finished diagram in one call: a database schema, an API specification, infrastructure code, a container setup, a CI workflow, a table of numbers, a folder listing, or a git log. It reads the text you send and nothing else, so it runs no code, opens no file, and fetches nothing. It is for anyone, human or AI agent, who would rather draw the system from its source than from memory. ## TL;DR | Question | Answer | |---|---| | What can it read? | `sql`, `prisma`, `drizzle`, `openapi`, `terraform`, `terraform-plan`, `bicep`, `arm`, `pulumi`, `cloudformation`, `cdk`, `compose`, `kubernetes`, `helm`, `kustomize`, `backstage`, `structurizr`, `asyncapi`, `github-actions`, `gitlab-ci`, `azure-pipelines`, `csv`, `json`, `tree`, and `git-log`. An agent reads what each one takes with `get_diagram_syntax` and `topic` `import-formats`. | | What does it draw? | A schema from SQL, Prisma, or Drizzle; an endpoint access matrix, a map of its endpoints by tag, or one operation as a sequence from OpenAPI; a flow from Terraform files, a Terraform plan or state, Bicep, an ARM template, Pulumi, CloudFormation, the AWS CDK, Compose, Kubernetes, Helm, Kustomize, a Backstage catalog, AsyncAPI documents, or a GitHub Actions, GitLab CI/CD, or Azure Pipelines pipeline; a C4 diagram from a Structurizr workspace; a chart from CSV or JSON; a tree from a listing; a git graph or a timeline from a log. | | How do I call it? | With `format` and `text`, or `files` for a folder such as a Terraform stack with its modules and environments, and optionally `title` and `options`. Over HTTP it is `POST /v1/import`; on the command line, `npx @buun_group/plotwork import-source <format> <file or folder>`. | | What comes back? | The Plotwork source, a diagnostic for everything left out with its line and column in your file, `coverage` (how much of the file the picture shows), the validation, an outline, and a `viewUrl` to open in a browser. A file too big for one picture also comes back in `parts`. | | Does it look finished? | Yes. Boxes get types, product icons, and typed frames; arrows show their relationship with an icon and a line colour; every import has a title and a description; and every sample on this page draws with no warning. | | What if the file is too big? | The hosted service reads up to 10 MiB in one call; the local server and the command line read far more. A file too big for one readable picture is split into several diagrams in `parts`, never cut; an answer holds 24 of them, and `partsPage` asks for the next. | | What if it cannot read the file? | Nothing is imported: `ok` is false, and an `I1xx` diagnostic says where and why, with a hint. It never throws. | | Is my file stored? | No. The text is worked on in memory and not kept. | ## Ask your agent ```text Use Plotwork to draw our database from db/schema.sql. Import it with import_source, then fix anything the diagnostics say was left out. ``` ```text Use Plotwork to show which roles can call which endpoints in openapi.yaml, as a matrix. Do not guess a cell the spec does not state. ``` ```text Use Plotwork to draw our local stack from docker-compose.yml and our CI from .github/workflows/ci.yml, and put both pictures in docs/architecture.md. ``` ```text Use Plotwork to draw production from infra/terraform: send every .tf and .tfvars file under it with import_source, with options.root set to envs/prod. ``` ## How an import works The agent reads the file and sends its text with the format. The importer reads it, writes Plotwork source, and parses it; the tool then validates the source and outlines it, once each. | Step | What happens | |---|---| | Read | One reader per format. It builds the boxes, tables, rows, entries, or commits, and reports what it skips or cannot resolve, with the line and column in your file. | | Write | Labels are quoted and escaped, ids are made valid, and the caps are applied. Icons come from the bundled set only: a lookup table for each format, and the same rule lint uses to suggest an icon. | | Check | The source is parsed, validated, and outlined. An import that would not parse returns nothing rather than broken source. | | Count | `coverage` says how much of the file the picture shows, such as `Drawn 79 of 79 resources.` When something is left out, `complete` is false, the `next` sentence says so first, and `I206` is reported when much is missing, so a picture of part of a file is never taken for the whole. | A folder is sent as `files`, a list of `path` and `text`. A format that spans files (Terraform, Bicep, ARM, Pulumi, CloudFormation, the CDK, SQL, Prisma, Drizzle, Kubernetes, Helm, Kustomize, Backstage, AsyncAPI) reads every file with its endings as one text and passes over the rest with `I201`; a diagnostic names its file and its line in that file. Other formats read the first file. Then call `draw_diagram` on the source to draw it, edit it first, or place it in a document with `sync_markdown`. ## Arrows and frames An imported arrow names its relationship twice: in its label, and with an icon in the label and a line colour. The same relationship looks the same whichever format it came from. | Relationship | Label examples | Icon | Line | |---|---|---|---| | Reads or writes stored data | `data`, `storage`, `cache`, `mounts` | database, hard drive | Data (blue) | | Reads its settings | `reads` (a ConfigMap) | eye | Plain | | Sends or receives a message it does not wait for | `messages`, `dead letters`, an AsyncAPI message | send, inbox | Async (dotted) | | Grants access, or uses a credential | a role, `identity`, `policy`, `secret`, `reads` (a Secret) | key | Identity (violet) | | Reaches another over a private network | `private link`, `VNet integration` | lock | Private network (aqua) | | Depends on, provides, or consumes | `depends on`, `provides`, `consumes`, a chart version | link, plug | Plain | | Routes outside traffic | an Ingress host or path, `public IP` | globe | Plain | | Starts another | `triggers` | lightning | Plain | | Other relationships | `firewall`, `logs`, `telemetry`, `images`, `builds on`, `includes`, `patches` | shield, activity, download, layers, package, replace | Plain | A line colour is given only where it tells lines apart: when every line of a picture would have the same one, as in an AsyncAPI document where every line is a message, the lines stay plain and the icons say the rest. A legend names each colour used. An arrow with no label, such as a Compose `depends_on`, has no icon. Structurizr relationships keep their own description and technology, since a C4 line takes no icon. Every frame has a type, so it has its colour and icon: a Terraform resource drawn round its parts by what it is (a SQL server is data, a Service Bus namespace messaging, a vault security), modules, CDK constructs, and Pulumi components as resource groups, Kubernetes namespaces, Helm releases and charts, Kustomize overlays as environments, GitHub Actions jobs and pipeline stages as CI and CD, and Backstage domains, systems, and owner groups. A Structurizr boundary gets an icon and a tone. ## Database schemas `sql` reads `CREATE TABLE` and `ALTER TABLE ... ADD` from Postgres, MySQL, and SQLite: primary keys, foreign keys inline or at the table level, `UNIQUE`, and `NOT NULL`. A column is `nullable` unless it is `NOT NULL` or part of the primary key. A migration history can be sent whole: renames, dropped tables and columns, and tables created again are applied in order, so the picture is the schema the last migration leaves. A schema of more than six tables comes back with `I204` and ready groups for `options.tables` (names, or prefixes such as `org_*`), to draw it in parts that stay readable. A schema of more than 20 tables is drawn whole in `parts`: an overview of the parts and the foreign keys between them, then the tables of each schema name, cut by the tables that share keys when one is too big. `prisma` reads `schema.prisma` models, with each relation labelled by its field. `drizzle` reads `pgTable`, `sqliteTable`, and `mysqlTable` calls as text; the code is never run. **Diagram source** ```diagram schema "Database schema" desc="5 tables and the 4 foreign keys between them, imported from SQL." table customers { id uuid pk email "varchar(255)" unique full_name text created_at timestamptz } table products { id bigserial pk sku text unique name text price_cents integer tags "text[]" nullable } table orders { id bigserial pk customer_id uuid fk status text placed_at timestamptz nullable } table order_items { order_id bigint pk fk product_id bigint pk fk quantity integer unit_price_cents integer } table payments { id bigserial pk order_id bigint fk provider_ref text nullable amount_cents integer } orders.customer_id -> customers.id order_items.order_id -> orders.id order_items.product_id -> products.id payments.order_id -> orders.id ``` *Picture: Database schema: 5 tables, 4 relations. 5 tables and the 4 foreign keys between them, imported from SQL.* **Text description** **Tables:** - customers - id uuid (primary key) - email varchar(255) (unique) - full_name text - created_at timestamptz - products - id bigserial (primary key) - sku text (unique) - name text - price_cents integer - tags text[] (nullable) - orders - id bigserial (primary key) - customer_id uuid (foreign key) - status text - placed_at timestamptz (nullable) - order_items - order_id bigint (primary key, foreign key) - product_id bigint (primary key, foreign key) - quantity integer - unit_price_cents integer - payments - id bigserial (primary key) - order_id bigint (foreign key) - provider_ref text (nullable) - amount_cents integer **Relations:** - orders.customer_id references customers.id (many to one) - order_items.order_id references orders.id (one to one) - order_items.product_id references products.id (one to one) - payments.order_id references orders.id (many to one) ## API specifications `openapi` reads an OpenAPI 3 document in JSON or YAML. The default view, `endpoints`, is a matrix of each operation against the security schemes, OAuth scopes, and `x-roles` values the document names. A cell is `yes` when the operation's security grants it, `no` when its security is stated and does not, and `na` when the document does not say: the importer never guesses. A `No sign-in` column appears when an operation is public through `security: []`. With `options.view` set to `sequence` and `options.operationId` set to an operation, it draws that one call: the client, the identity provider when OAuth secures it, the API, and each response as a branch. **Diagram source** ```diagram matrix "Orders API access" desc="Who may call each of the 6 endpoints of the Orders API, by security scheme, scope, and role, imported from its OpenAPI document." columns "No sign-in" "orders:read" "partnerKey" "orders:write" "customer" "support" row "GET /health" sub="Check the service is up" : yes, yes, yes, yes, yes, yes row "GET /orders" sub="List orders" : no, yes, yes, no, yes, yes row "POST /orders" sub="Place an order" : no, no, no, yes, yes, no row "GET /orders/{orderId}" sub="Read one order" : no, yes, no, no, yes, yes row "DELETE /orders/{orderId}" sub="Cancel an order before it ships" : no, no, no, yes, yes, yes row "POST /orders/{orderId}/refunds" sub="Refund an order" : no, no, no, yes, no, yes legend yes="Allowed" no="Denied" ``` *Picture: Orders API access: 6 rows compared on 6 columns. Who may call each of the 6 endpoints of the Orders API, by security scheme, scope, and role, imported from its OpenAPI document.* **Text description** **Columns: No sign-in, orders:read, partnerKey, orders:write, customer, support.** - GET /health (Check the service is up): No sign-in: Allowed; orders:read: Allowed; partnerKey: Allowed; orders:write: Allowed; customer: Allowed; support: Allowed. - GET /orders (List orders): No sign-in: Denied; orders:read: Allowed; partnerKey: Allowed; orders:write: Denied; customer: Allowed; support: Allowed. - POST /orders (Place an order): No sign-in: Denied; orders:read: Denied; partnerKey: Denied; orders:write: Allowed; customer: Allowed; support: Denied. - GET /orders/{orderId} (Read one order): No sign-in: Denied; orders:read: Allowed; partnerKey: Denied; orders:write: Denied; customer: Allowed; support: Allowed. - DELETE /orders/{orderId} (Cancel an order before it ships): No sign-in: Denied; orders:read: Denied; partnerKey: Denied; orders:write: Allowed; customer: Allowed; support: Allowed. - POST /orders/{orderId}/refunds (Refund an order): No sign-in: Denied; orders:read: Denied; partnerKey: Denied; orders:write: Allowed; customer: Denied; support: Allowed. With `options.view` set to `map`, the endpoints are drawn as a tree: a folder for each tag, in the order the document's `tags` list gives them, and a row for each endpoint with its summary. The path every endpoint of a tag shares is named once on its folder, so long paths stay readable. An endpoint with several tags is drawn once, under its first. A document whose operations state no security and no `x-roles` has nothing for the columns of a matrix, so the default view draws the map instead and says so with `I203`. A big API is never cut short. A matrix of more than 20 endpoints, or a map of more than 60 rows, is split by tag into several diagrams, returned in `parts` with `I204`; whole tags stay together, and a tag too big for one diagram is cut into even pieces titled `(1 of 2)`. `options.tags` draws only the operations with one of the tags, and `options.paths` only those under one of the path prefixes, such as `/orders`; what they leave out is counted in `coverage` and named with `I201`. Only what the pictures need is read: paths, operations, tags, security schemes and requirements, and `x-roles`. In YAML the bodies of `components.schemas` and `components.examples` are skipped before parsing, and a `$ref` is followed only for the one operation a sequence draws, so a document of several megabytes imports in well under a second on the local server. ## Infrastructure as code `terraform` reads the `.tf`, `.tf.json`, and `.tfvars` files of a stack, best sent whole in `files`, with its modules and its environments. Every `resource` and `module` block is drawn, nothing is summed into a count, and `coverage` says `Drawn N of N resources`: | What the stack says | What is drawn | |---|---| | A resource names its resource group, network, subnet, plan, or environment (`resource_group_name`, `subnet_id`, `service_plan_id`) | It sits in that frame, the most specific one: a plan or an environment, then a subnet, a network, a resource group, and the account of the `provider` block. A frame named by a variable, such as `subnet_id = var.endpoint_subnet_id`, is drawn with the variable's name. | | A resource names the one that holds it (`key_vault_id`, `server_id`, `storage_account_name`, an API in its API Management) | It sits in a frame around its holder, so a vault holds its secrets and a server its databases. | | A role assignment or an IAM member | An arrow from the principal to the scope, labelled with the role. Several roles between the same two are one arrow, `3 roles`. A role given to principals the stack does not declare is a box named by the role, inside its scope. | | A private endpoint | A box in its subnet, with a `private link` arrow to what it reaches. | | A diagnostic setting, a DNS link, a listener, an association | An arrow labelled `diagnostics`, `DNS link`, the protocol and port, or drawn by nesting, such as a security group in the subnet it secures. | | Any other reference | An arrow labelled by its attribute (`identity`, `storage`, `logs`) or by what it reaches (`secret`, `data`, `messages`). | Types of AWS, Azure, and Google Cloud get their product icons from a table, and any other type of those providers is read from its name, so a new resource type still gets a fitting icon. A resource of another provider, such as `random_password`, is a plain box. Send the `.tfvars` files too, and names are drawn as they become: `name = "kv-${local.base}"` is drawn `kv-shop-dev`, and a subnet named by `var.endpoint_subnet_id` is drawn with the name at the end of its id. Variable defaults, `terraform.tfvars`, and every `*.auto.tfvars` apply as Terraform applies them; an environment's file, such as `env/dev.tfvars`, applies when it is the only one sent, or when `options.varFiles` names it (`--var-file` on the command line). A variable marked sensitive is never drawn. A variable that is named but never declared frames nothing: the resource sits in the account, with `I202` on it. With `options.groupBy` set to `file`, resources are framed by the file that declares them instead. When three or more boxes of one frame have the same arrow to one box, such as five jobs pulling images from one registry, they are drawn as one arrow from the frame's own box, `images (5)`. A stack of more than 30 boxes comes back in `parts`: first an overview with one box for each resource group and one counted arrow for everything between two groups, then one diagram for each group with everything in it. An arrow to a box of another part ends on a dashed box that names the part. **Diagram source** ```diagram flow "Azure infrastructure" direction=right provider=azure desc="12 resources on Azure, nested in the groups, networks, and subnets that hold them, imported from Terraform. An arrow means one resource refers to another, and its label says how: a role, a private link, logs, or a secret." group azure_account "Azure subscription" type=account { group app "orders-prod" type=resource-group sub="westeurope" { node main_4 storage "ordersfuncstore" sub="Storage account Standard" icon=azure:storage-accounts node main_5 secret "orders-kv" sub="Key Vault standard" icon=azure:key-vault group main "orders-vnet" type=network sub="10.20.0.0/16" { group apps "apps" type=subnet sub="10.20.1.0/24" { node apps_subnet service "Subnet" icon=azure:subnet } } group main_2 "orders-plan" type=service-plan sub="P1v3 Linux" { node api service "orders-api" sub="App Service" icon=azure:app-service node worker function "orders-worker" sub="Function app" icon=azure:functions } group main_group "orders-sql" icon=azure:sql-server { node main_3 database "SQL server" icon=azure:sql-server node orders database "orders" sub="SQL database S1" icon=azure:sql-database } group main_group_2 "orders-bus" icon=azure:service-bus sub="Service Bus namespace, Standard" { node orders_2 queue "orders" sub="Service Bus queue" icon=azure:service-bus } } } api -> apps_subnet : "VNet integration" api -> main_3 : "data" api -> orders : "data" api -> orders_2 : "messages" api -> main_5 : "secret" worker -> main_4 : "storage" worker -> orders_2 : "messages" ``` *Picture: Azure infrastructure: 8 nodes, 7 connections. 12 resources on Azure, nested in the groups, networks, and subnets that hold them, imported from Terraform. An arrow means one resource refers to another, and its label says how: a role, a private link, logs, or a secret.* **Text description** **Nodes:** - Account: Azure subscription - Resource group: orders-prod (westeurope) - ordersfuncstore (Storage account Standard), storage - orders-kv (Key Vault standard), secret - Virtual network: orders-vnet (10.20.0.0/16) - Subnet: apps (10.20.1.0/24) - Subnet, service - Service plan: orders-plan (P1v3 Linux) - orders-api (App Service), service - orders-worker (Function app), function - Group: orders-sql - SQL server, database - orders (SQL database S1), database - Group: orders-bus (Service Bus namespace, Standard) - orders (Service Bus queue), queue **Connections:** - orders-api to Subnet: VNet integration - orders-api to SQL server: data - orders-api to orders: data - orders-api to orders: messages - orders-api to orders-kv: secret - orders-worker to ordersfuncstore: storage - orders-worker to orders: messages ### Terraform in folders A stack laid out as enterprises keep one, `modules/network`, `modules/data`, and `envs/dev` and `envs/prod` that call them, is read as Terraform plans it: | What the folder holds | What is drawn | |---|---| | `module "network" { source = "../../modules/network" }`, with the module's files sent | A frame of the module around its resources, named as Terraform names them, `module.network.azurerm_subnet.this["app"]`. The module's variables take what the call passes, and `module.network.subnet_ids["app"]` is followed through the output to the subnet. A module from a registry or git, or one whose files were not sent, is one box. | | Several root modules, such as `envs/dev` and `envs/prod` | One diagram for each, in `parts`, each with the `terraform.tfvars` of its own folder. `options.root` (`--root` on the command line) draws one: `envs/prod`, or `prod`. | | `for_each` or `count` whose keys are in the text: a map or set literal, a variable's value, a local, or a number | One box for each copy, `aws_subnet.private["a"]`, named with `each.key` and `count.index`. A reference with an index points at that copy; one without points at every copy. Keys from outside the text, or more than 12, are one box marked `for each`. A block that makes no copy, such as `count = var.enabled ? 1 : 0` with `enabled` false, is left out with `I201`. | | `format()`, `join()`, `lookup()`, `merge()`, `replace()`, and conditionals in names and tags | Resolved when their inputs are in the text: `format("kv-%s-%s", local.org, var.env)` is `kv-acme-prod`. A part that is not is kept by name, `kv-{prefix}-shared`. | | `depends_on` | A dashed `depends on` arrow, only where nothing else joins the two. | | Provider blocks with `alias` in different regions | A region frame for each inside the account. | `options.groupBy` set to `network` nests by network alone, without module frames. A frame of the network module sits in the resource group its resources name, and a database of the data module placed in a subnet of the network module points at that subnet with a `subnet` arrow. ### Terraform plans and state `terraform-plan` reads what Terraform itself writes, so values that come from outside the `.tf` files, such as a registry module's resources or a name a data source returns, are drawn as Terraform resolved them: | What you send | What is drawn | |---|---| | The output of `terraform show -json plan.out`, after `terraform plan -out=plan.out` | The stack as the plan leaves it. Each box the plan changes ends its second line with a word, in a tone: `create` green, `update` amber, `replace` orange, `destroy` red, `import` and `move` aqua, `forget` violet. A box that is created, updated, replaced, or destroyed also carries the `change=` mark, a badge with `+`, `~`, `±`, or `−` and an outline in its tone, so the change shows on a box with a vendor icon too. The title counts the changes as Terraform does, `AWS infrastructure: 3 to add, 4 to change, 3 to destroy`. A resource the plan destroys is drawn from the plan's prior state. A resource changed outside Terraform since the last apply says `drifted`. | | The output of `terraform show -json`, or a `terraform.tfstate` file | The stack as it was last applied, with no tone. | Every resource keeps the address the `terraform` reader gives it, such as `module.network.aws_subnet.private["eu-west-1a"]`, so the overrides of a diagram made from the `.tf` files carry over to one made from a plan. A plan's configuration gives the arrows, followed through module outputs and the variables a call passes; when a reference names every copy of a block, the known value chooses the copy, so each machine sits in its own subnet. A state has no configuration: a value that holds another resource's id is the arrow, and a dependency no value shows is a dashed `depends on` arrow, left out when another arrow already leads there. The plan's JSON does not expand local values, so an arrow made through a `local` is left out with `I202`. A sensitive value or variable is never drawn. ### Bicep and ARM templates `bicep` reads a deployment: `main.bicep`, the module files it uses (sent in `files`, at their paths), and its `.bicepparam` values. Every resource is drawn as the Terraform resource that makes the same thing, so `Microsoft.KeyVault/vaults` is the same Key Vault box, with the same frames and arrow labels, as `azurerm_key_vault`. A module whose file is sent is a frame in the resource group its `scope:` names; resources nested in a parent, or with `parent:`, sit in it; subnets written inline in a network are frames; `existing` resources say so. Names are drawn as they become, with `format('{0}-{1}-api', prefix, env)` and `'${prefix}-web'` resolved and `uniqueString()` kept by name. A loop over a known list is one box for each item, and a condition the values make false leaves the resource out with `I201`. A `.bicepparam` file applies when it is the only one sent, or when `options.varFiles` names it. `arm` reads an ARM template, as `az bicep build` or the portal writes it, with its parameters file, and draws it as the Bicep reader does: `parameters()`, `variables()`, `copy` loops, `condition`, `dependsOn`, and child resources named `parent/child`. **Diagram source** ```diagram flow "Invoicing on Azure" direction=down provider=azure desc="8 resources on Azure, nested in the groups, networks, and subnets that hold them, imported from Bicep. An arrow means one resource refers to another, and its label says how: a role, a private link, logs, or a secret." group azure_account "Azure subscription" type=account { group resource_group "Resource group" type=resource-group { node workspace service "log-ledger-prod" sub="Log Analytics workspace" icon=azure:log-analytics-workspaces node insights service "appi-ledger-prod" sub="Application Insights" icon=azure:application-insights node vault secret "kv-ledger-prod" sub="Key Vault" icon=azure:key-vault group storage_group "stledgerprod{uniqueString}" icon=azure:storage-accounts { node storage storage "Storage account" icon=azure:storage-accounts node invoices queue "invoices" sub="Storage queue" icon=azure:storage-queue } group plan "asp-ledger-prod" type=service-plan sub="Y1" { node job function "func-ledger-prod-invoices" sub="Function app" icon=azure:functions } } } insights -> workspace : "logs" job -> storage : "storage" job -> insights : "telemetry" job -> vault : "Key Vault Secrets User, secret" invoices -> storage : "part of" ``` *Picture: Invoicing on Azure: 6 nodes, 5 connections. 8 resources on Azure, nested in the groups, networks, and subnets that hold them, imported from Bicep. An arrow means one resource refers to another, and its label says how: a role, a private link, logs, or a secret.* **Text description** **Nodes:** - Account: Azure subscription - Resource group: Resource group - log-ledger-prod (Log Analytics workspace), service - appi-ledger-prod (Application Insights), service - kv-ledger-prod (Key Vault), secret - Group: stledgerprod{uniqueString} - Storage account, storage - invoices (Storage queue), queue - Service plan: asp-ledger-prod (Y1) - func-ledger-prod-invoices (Function app), function **Connections:** - appi-ledger-prod to log-ledger-prod: logs - func-ledger-prod-invoices to Storage account: storage - func-ledger-prod-invoices to appi-ledger-prod: telemetry - func-ledger-prod-invoices to kv-ledger-prod: Key Vault Secrets User, secret - invoices to Storage account: part of ### Pulumi `pulumi` reads three things, and draws each resource as the Terraform resource that makes the same thing, so AWS, Azure, and Google Cloud look the same whichever tool wrote them: | What you send | What is read | |---|---| | `Pulumi.yaml` of a YAML program, with its `Pulumi.<stack>.yaml` | Resources by type token, `${resource.property}` references, variables, configuration, `fn::join`, `fn::select`, and `fn::split`, and `options.dependsOn` and `options.parent`. | | The output of `pulumi stack export` | Every resource of a program in TypeScript, Python, Go, C#, or Java, without reading the code: its parent, its dependencies by property, and its components as frames. | | The output of `pulumi preview --json` | The stack as the update will leave it; a step that deletes is counted with `I201`, not drawn. | A resource is named by its logical name, or by its URN when two share one. A secret is never drawn. With no component to frame it, the network is a layer of its own: an app or a database placed in a subnet sits beside the network with a `subnet` arrow, as a network module draws it in Terraform and Bicep. ### CloudFormation and SAM `cloudformation` reads a template in YAML, with its short tags, or JSON. `!Ref`, `!GetAtt`, and `!Sub` are arrows named by the property that holds them, names are drawn as `!Sub`, `!Join`, and parameter defaults make them, a `Condition` the defaults make false leaves the resource out with `I201`, and `DependsOn` is a dashed arrow. A SAM function's API event is an arrow from the API labelled with its route, `POST /orders`, and a queue or stream event a `triggers` arrow from its source. A resource is named by its logical id. ### AWS CDK `cdk` reads the cloud assembly `cdk synth` writes: send the `cdk.out` folder (`manifest.json`, `tree.json`, each stack's template, and its nested stacks' templates), or one template `cdk synth` prints. Each resource is read as `cloudformation` reads it, so a bucket made with the CDK is the same box as one written in CloudFormation or Terraform. | What | How it is drawn | |---|---| | Stacks | Frames, when the app has several, with arrows across them through `Fn::ImportValue`. `options.root` draws one stack alone, titled by its name. | | Nested stacks | Frames inside their parent, with the parameters it passes and the outputs it reads followed both ways. One whose template was not sent is a box, with `I202`. | | Your own constructs | Frames, by construct path. A construct that wraps a resource of its own, such as a `Function` or a `Vpc`, is that resource's box, and what it makes besides (its role and policy, its subnets) sits beside it in the same frame. | | Names | Construct paths: `OrdersApi/Ordering/Handler` for a box, `OrdersApi/Ordering` for a frame. | `AWS::CDK::Metadata` and the asset manifests are passed over, and a stack the manifest names whose template was not sent is `I201`. ## Containers `compose` reads a Docker Compose file. Each service is a box whose type and brand mark come from its image, so `postgres:16` is a database with the Postgres mark. An arrow goes from a service to each service it depends on, through `depends_on`, `links`, or an address in its environment such as `postgres://app@db:5432`, and a service that shares another's network through `network_mode: "service:db"` sits in its frame with a `shares network` arrow. Every network the file declares becomes a frame, and named volumes become storage boxes. Variables are read as Compose reads them with nothing set: `${PORT:-8080}` and `${PORT-8080}` read as their default, `${FLAG:+on}` as nothing, and a variable with no default (`${TAG}`, `$TAG`) stays as `$TAG`, so the box shows the value is set elsewhere. `kubernetes` reads multi-document manifests the same way: workloads, Ingresses, ConfigMaps, Secrets, and claims become boxes, namespaces become frames, and routes, selectors, addresses, and mounts become arrows. `helm` reads what `helm template` writes and draws it as `kubernetes` would, framed by the labels Helm adds: the release (`app.kubernetes.io/instance`), and, in a release of several charts such as an umbrella chart and its subcharts, the chart (`helm.sh/chart`); `app.kubernetes.io/part-of` frames the releases of one application. Sent a chart folder instead, it draws the charts (`Chart.yaml`, and those in `charts/`) and what each depends on, each arrow labelled with the version `Chart.lock` resolved, and a dependency `values.yaml` switches off dashed. The templates themselves are not rendered, and `I201` says so. `kustomize` reads what `kustomize build` writes and draws it as `kubernetes` would, with the hash a generator adds to a ConfigMap's or Secret's name taken off, so the box keeps its name when its data changes. Sent a folder of `kustomization.yaml` files instead, it draws each overlay, base, and component as a frame of the files, patches, and generators it lists, with arrows for what an overlay `builds on` and `includes`, and what each patch `patches`. **Diagram source** ```diagram flow "shop services" direction=down desc="The 6 services of a Docker Compose file in 2 networks, with 1 named volume. Each arrow points from a service to one it depends on." node db_data_volume storage "db-data" sub="Named volume" group edge_net "edge" type=network { node proxy gateway "proxy" sub="nginx:1.27-alpine, ports 80, 443" icon=simple:nginx node web service "web" sub="build ./web" node api service "api" sub="build ./api" } group data_net "data" type=network { node worker service "worker" sub="build ./api, 2 replicas" icon=lucide:server node db_1 database "db" sub="postgres:16-alpine" icon=simple:postgresql node cache cache "cache" sub="redis:7" icon=simple:redis } proxy -> web proxy -> api web -> api api -> db_1 api -> cache worker -> db_1 worker -> cache db_1 -> db_data_volume : "mounts" role=data ``` *Picture: shop services: 7 nodes, 8 connections. The 6 services of a Docker Compose file in 2 networks, with 1 named volume. Each arrow points from a service to one it depends on.* **Text description** **Nodes:** - db-data (Named volume), storage - Virtual network: edge - proxy (nginx:1.27-alpine, ports 80, 443), gateway - web (build ./web), service - api (build ./api), service - Virtual network: data - worker (build ./api, 2 replicas), service - db (postgres:16-alpine), database - cache (redis:7), cache **Connections:** - proxy to web - proxy to api - web to api - api to db - api to cache - worker to db - worker to cache - db to db-data (stored data line): mounts ## Software catalogs `backstage` reads Backstage catalog descriptors: one `catalog-info.yaml` of many documents, or every descriptor of a folder. Components, APIs, and resources are boxes typed by what they are (a `website` is a client, an `openapi` API carries the OpenAPI mark, a `database` resource is a database), systems are frames, and domains are frames around their systems. An arrow says a component `provides` or `consumes` an API, or `depends on` another entity, and a subcomponent is a dashed `part of`. Each box names its owner on its second line; with `options.groupBy` set to `owner`, the groups that own the entities are the frames instead, nested by their parent group, with their members named on the tab. Everything is named by its entity reference, such as `component:default/orders-api`. A `Location` is not followed, a kind such as `Template` is left out with `I201`, and a relation to an entity the files do not describe is `I202`. ## Architecture models `structurizr` reads a Structurizr workspace, written in its DSL (`workspace.dsl`) or exported as JSON, and draws one view of it as a C4 diagram. The view is the first `systemContext`, `container`, or `component` view the workspace defines, or the containers of its first system that has some; `options.view` (`context`, `container`, or `component`) and `options.root` (an identifier or a name, such as `banking.api`) choose another. A view holds what Structurizr's `include *` holds: the elements it is about and every person, system, or container a relationship joins to them, with a relationship of a component drawn as one of its container at the container level. | What | How it is drawn | |---|---| | People, software systems, containers, components | C4 elements, with their description and technology. A container or component tagged `Database`, styled as a cylinder, or built on a database technology is a database. | | The system or container the view is about | A boundary round its children. What a card holds is counted on it: `Holds 5 containers and 3 components.` | | Groups and the enterprise | `group` and `enterprise` boundaries. A system inside the enterprise is the company's own; one outside it, or tagged `External`, is external. | | Relationships | Lines with their description and technology, dashed when tagged `Asynchronous`, each drawn once. | | Identifiers | Flat or `!identifiers hierarchical`, with `this`, `-> x` inside an element's block, `!element`, and `!const` read. They are the stable names: `banking.api`. | `autolayout tb` or `lr` of the view sets the direction. An element the view does not reach is `I201`, with how to draw the view that holds it; a deployment environment is `I201`, and an `!include` or `workspace extends` is named, not fetched. **Diagram source** ```diagram c4 "Online banking: containers" view=container direction=down desc="The containers of Online banking and what they talk to, imported from the Structurizr workspace Harbour Bank." person customer "Personal customer" desc="A customer with a current or savings account." person agent "Support agent" desc="Answers customers in the contact centre." boundary banking "Online banking" kind=system { container web "Web app" tech="React" desc="The banking pages in the browser." container api "Banking API" tech="Kotlin, Spring Boot" desc="Serves accounts, payments, and statements as JSON. Holds 3 components." db store "Banking database" tech="PostgreSQL" desc="Customers, sessions, and audit events." boundary payments "Payments" kind=group { container worker "Payment worker" tech="Go" desc="Sends payment orders to the card scheme in batches." container orders "Payment orders" tech="RabbitMQ" desc="Holds payment orders until they are sent." } } system ledger "Core ledger" desc="The bank's system of record for every account." external=on system cards "Card scheme" desc="Clears card and account-to-account payments." external=on customer -> web : "Signs in and pays" tech="HTTPS" agent -> web : "Looks up a customer" tech="HTTPS" web -> api : "Calls" tech="JSON/HTTPS" api -> store : "Reads sessions" tech="JDBC" api -> orders : "Queues payment orders" tech="AMQP" api -> ledger : "Reads balances and posts entries" tech="MQ" worker -> orders : "Takes payment orders" tech="AMQP" worker -> cards : "Sends payments" tech="ISO 20022" ``` *Picture: Online banking: containers: container view, 9 elements, 2 boundaries, 8 relationships. The containers of Online banking and what they talk to, imported from the Structurizr workspace Harbour Bank.* **Text description** **Elements:** - Personal customer, person (A customer with a current or savings account.) - Support agent, person (Answers customers in the contact centre.) - Core ledger, external system (The bank's system of record for every account.) - Card scheme, external system (Clears card and account-to-account payments.) - Boundary: Online banking, software system - Web app, container, React (The banking pages in the browser.) - Banking API, container, Kotlin, Spring Boot (Serves accounts, payments, and statements as JSON. Holds 3 components.) - Banking database, database, PostgreSQL (Customers, sessions, and audit events.) - Boundary: Payments, group - Payment worker, container, Go (Sends payment orders to the card scheme in batches.) - Payment orders, container, RabbitMQ (Holds payment orders until they are sent.) **Relationships:** - Personal customer to Web app: Signs in and pays, HTTPS - Support agent to Web app: Looks up a customer, HTTPS - Web app to Banking API: Calls, JSON/HTTPS - Banking API to Banking database: Reads sessions, JDBC - Banking API to Payment orders: Queues payment orders, AMQP - Banking API to Core ledger: Reads balances and posts entries, MQ - Payment worker to Payment orders: Takes payment orders, AMQP - Payment worker to Card scheme: Sends payments, ISO 20022 ## Event-driven systems `asyncapi` reads AsyncAPI 2.x and 3.x documents in YAML or JSON, one application each. Send one, or every document of a folder in `files`: channels of one address are one box, so the picture shows where the producers and consumers of an event-driven system meet. Each application is a service, each channel a queue in the frame of its broker, and each operation an arrow labelled with its messages: into the channel for what the application sends, out of it for what it receives. | What | How it is read | |---|---| | 3.x operations | `action: send` and `action: receive`, on the channel they reference, with their `messages`, or every message of the channel. | | 2.x operations | Inverted, as the specification words them from the other side: `subscribe` is what the application sends, and `publish` is what it receives. | | Servers | A frame for each kind of broker (`kafka` and `kafka-secure` are Kafka), with its host; a channel sits in it when its servers are of that kind. The picture runs down from what is sent to what reads it, so a broker is drawn once for each step that holds its channels, and a channel on a loop (an application that reads what its own sends lead to) stands outside the frame with the broker's mark. No frame holds both a channel an application sends to and one it reads, so no arrow runs round a frame. The Kafka, Redis, Pulsar, Amazon SNS and SQS, and Google Pub/Sub marks are drawn from the protocol; another mark only when the server's name names the product. | | Names | `app:` and the title, `channel:` and the address, `broker:` and the protocol. | An operation whose channel is not in its document is `I202`, a server that holds no drawn channel is `I201`, and a `$ref` into another file is read for its name only. **Diagram source** ```diagram flow "Event flows" direction=down desc="4 applications and the 5 channels where they meet: an arrow into a channel sends to it, an arrow out of one receives from it, labelled with the messages. Imported from AsyncAPI." node checkout_service service "Checkout service" sub="Version 2.4.0" node orders_placed_v1 queue "orders.placed.v1" sub="One event per order that was paid for." icon=simple:apachekafka node orders_cancelled_v1 queue "orders.cancelled.v1" sub="An order the customer or support cancelled." icon=simple:apachekafka node payments_settled_v1 queue "payments.settled.v1" icon=simple:apachekafka node stock_reserved_v1 queue "stock.reserved.v1" icon=simple:apachekafka node payments_service service "Payments service" sub="Version 1.12.3" node warehouse_service service "Warehouse service" sub="Version 3.1.0" node notification_service service "Notification service" sub="Version 0.20.1" group kafka "Kafka" type=messaging icon=simple:apachekafka sub="2 hosts" { node payments_refunds_v1 queue "payments.refunds.v1" sub="Refunds asked for by support." } checkout_service -> orders_placed_v1 : "OrderPlaced" checkout_service -> orders_cancelled_v1 : "OrderCancelled" payments_settled_v1 -> checkout_service : "PaymentSettled" stock_reserved_v1 -> checkout_service : "StockReserved" orders_placed_v1 -> notification_service : "OrderPlaced" payments_settled_v1 -> notification_service : "PaymentSettled, PaymentFailed" orders_placed_v1 -> payments_service : "OrderPlaced" payments_service -> payments_settled_v1 : "PaymentSettled, PaymentFailed" payments_refunds_v1 -> payments_service : "RefundRequested" orders_placed_v1 -> warehouse_service : "OrderPlaced" orders_cancelled_v1 -> warehouse_service : "OrderCancelled" warehouse_service -> stock_reserved_v1 : "StockReserved" ``` *Picture: Event flows: 9 nodes, 12 connections. 4 applications and the 5 channels where they meet: an arrow into a channel sends to it, an arrow out of one receives from it, labelled with the messages. Imported from AsyncAPI.* **Text description** **Nodes:** - Checkout service (Version 2.4.0), service - orders.placed.v1 (One event per order that was paid for.), queue - orders.cancelled.v1 (An order the customer or support cancelled.), queue - payments.settled.v1, queue - stock.reserved.v1, queue - Payments service (Version 1.12.3), service - Warehouse service (Version 3.1.0), service - Notification service (Version 0.20.1), service - Messaging: Kafka (2 hosts) - payments.refunds.v1 (Refunds asked for by support.), queue **Connections:** - Checkout service to orders.placed.v1: OrderPlaced - Checkout service to orders.cancelled.v1: OrderCancelled - payments.settled.v1 to Checkout service: PaymentSettled - stock.reserved.v1 to Checkout service: StockReserved - orders.placed.v1 to Notification service: OrderPlaced - payments.settled.v1 to Notification service: PaymentSettled, PaymentFailed - orders.placed.v1 to Payments service: OrderPlaced - Payments service to payments.settled.v1: PaymentSettled, PaymentFailed - payments.refunds.v1 to Payments service: RefundRequested - orders.placed.v1 to Warehouse service: OrderPlaced - orders.cancelled.v1 to Warehouse service: OrderCancelled - Warehouse service to stock.reserved.v1: StockReserved ## Pipelines `github-actions` reads a workflow file. What starts it (`push`, `pull_request`, a schedule, or a manual run) is the first box, and each job follows in the order its `needs` set. When the steps fit, each job is a frame of its steps; otherwise a job is one box that says how many steps it runs. **Diagram source** ```diagram flow "CI" direction=right desc="4 jobs of the CI workflow, started by a push to main or a pull request to main, in the order their needs set, imported from GitHub Actions." node on terminal "Push or pull request" node lint step "Lint and type check" sub="ubuntu-latest, 6 steps" icon=simple:pnpm node test step "Unit tests" sub="4 matrix runs, 6 steps" icon=simple:pnpm node e2e step "Browser tests" sub="ubuntu-latest, 5 steps" icon=simple:pnpm node build step "Build image" sub="ubuntu-latest, 3 steps" icon=simple:docker on -> lint on -> test lint -> e2e test -> build e2e -> build ``` *Picture: CI: 5 nodes, 5 connections. 4 jobs of the CI workflow, started by a push to main or a pull request to main, in the order their needs set, imported from GitHub Actions.* **Text description** **Nodes:** - Push or pull request, terminal - Lint and type check (ubuntu-latest, 6 steps), step - Unit tests (4 matrix runs, 6 steps), step - Browser tests (ubuntu-latest, 5 steps), step - Build image (ubuntu-latest, 3 steps), step **Connections:** - Push or pull request to Lint and type check - Push or pull request to Unit tests - Lint and type check to Browser tests - Unit tests to Build image - Browser tests to Build image `gitlab-ci` reads a `.gitlab-ci.yml`. Its stages are frames down the page in the order of `stages` (GitLab's `.pre`, `build`, `test`, `deploy`, `.post` when it lists none), and its jobs sit inside them. A job with `needs` is reached from each job it needs, and `needs: []` from the start; a job without is reached from every job of the stage before, which is when GitLab starts it. A job that runs by hand, by its `rules`, or `only` on some refs is reached by dashed arrows and says so. `extends`, hidden `.jobs`, and anchors are resolved and not drawn; `parallel` and `parallel: matrix` are counted; a `trigger:` job points at the child or downstream pipeline it starts. Each `include:` is named in `I201`, not fetched. `azure-pipelines` reads an `azure-pipelines.yml`: stages as frames, jobs and deployment jobs inside, a deployment naming its environment, and arrows for `dependsOn`, a name or a list, at both levels. A stage with no `dependsOn` waits for the one before it, and `dependsOn: []` starts with the pipeline; a `condition:` makes the arrows into a job dashed. A file of only jobs draws without frames, and a file of only steps as one chain. Templates, in a list or as `extends: template:`, are named in `I201`, not fetched. Neither pipeline is told apart from other YAML by its text: name the file (`.gitlab-ci.yml`, `azure-pipelines.yml`) or set `format`. ## Tables of data `csv` reads comma, tab, or semicolon separated text with quoted fields, and `json` reads an array of records, rows with a header, an object of names and numbers, or nested objects of numbers. With `options.chart` left at `auto`, the shape of the table chooses the chart. A table too big for one chart is drawn as several in `parts`, never cut: the places of an XY chart in pages of up to 60 and its series in groups of six, a matrix in pages of 20 rows and 8 columns, a pie of more than 30 slices in pages that each keep one slice for all the others, so every pie is of the whole, flows on several sankeys (a flow back to an earlier node on a sankey of its own, since a sankey only goes forward), and tiles cut along their groups. | Shape of the table | Chart | |---|---| | A source, a target, and a value column | `sankey` | | A path column (`a/b/c`), or two or more text columns and one number; nested JSON objects of numbers | `treemap` | | Names, then columns of yes and no | `matrix` | | One category and one number that are shares of a whole | `pie` | | A category and one or more number columns | `xychart`, with lines when the categories are times | Currency signs, thousands separators, and percent signs are read, and the unit goes on the chart. **Diagram source** ```diagram xychart "P50 ms and p95 ms by endpoint" direction=horizontal desc="p50 ms and p95 ms for each endpoint, 5 in all, imported from a table." x "Endpoint" ["GET /products" "GET /products/{id}" "POST /cart/items" "POST /checkout" "GET /orders"] bar "p50 ms" [42 31 58 145 67] bar "p95 ms" [118 96 171 388 204] ``` *Picture: P50 ms and p95 ms by endpoint: 2 series (bar) over 5 places on the Endpoint axis, on a scale of 0 to 400. p50 ms and p95 ms for each endpoint, 5 in all, imported from a table.* **Text description** **X axis, Endpoint: GET /products, GET /products/{id}, POST /cart/items, POST /checkout, GET /orders.** **Y axis: 0 to 400.** **Drawn with the places down the side.** **Series:** - p50 ms (bar): GET /products 42, GET /products/{id} 31, POST /cart/items 58, POST /checkout 145, GET /orders 67 - p95 ms (bar): GET /products 118, GET /products/{id} 96, POST /cart/items 171, POST /checkout 388, GET /orders 204 ## Folder listings `tree` reads the output of the `tree` command, a list of paths such as the output of `git ls-files`, or an indented outline. Files and folders get their icons; an outline of plain words becomes an outline tree. A listing of more than 60 rows, or deeper than six levels, is drawn as several trees in `parts`, cut along its folders: each part names the path of the folder it is in (`src/lib`), and no entry is left out. **Diagram source** ```diagram tree "docs-site" desc="The folders and files of docs-site, 18 entries, imported from a tree listing." dir "content" { dir "guides" { file "install.md" file "quick-start.md" } dir "reference" { file "api.md" } } dir "public" { file "favicon.svg" file "logo.png" } dir "src" { dir "components" { file "Nav.tsx" file "Page.tsx" } file "main.tsx" file "styles.css" } file "README.md" file "tsconfig.json" file "vite.config.ts" ``` *Picture: docs-site: file tree of 18 entries (6 folders, 12 files), 3 levels. The folders and files of docs-site, 18 entries, imported from a tree listing.* **Text description** - content/ (folder, 2 entries) - content/guides/ (folder, 2 entries) - content/guides/install.md - content/guides/quick-start.md - content/reference/ (folder, 1 entry) - content/reference/api.md - public/ (folder, 2 entries) - public/favicon.svg - public/logo.png - src/ (folder, 3 entries) - src/components/ (folder, 2 entries) - src/components/Nav.tsx - src/components/Page.tsx - src/main.tsx - src/styles.css - README.md - tsconfig.json - vite.config.ts ## Git history `git-log` reads `git log --oneline --graph --decorate`, plain `git log`, or `--oneline`. A log with a graph or decorations becomes a git graph: lanes from the graph, branch names from decorations and merge messages, and tags. A plain log with dates becomes a timeline of commits by day, or by month when it spans more than about twelve days. `options.view` chooses either. A longer history is drawn in pages of up to 40 consecutive commits, newest first, each a git graph of its own; a timeline draws a busy day or month as several periods of five commits, and a long one in pages. No commit is left out. **Diagram source** ```diagram git "Repository history" desc="8 commits on 3 branches, with 2 merges and 2 tags, imported from git log." commit "Initial commit" commit "Add sign in with email" id=c9d0e1f2 branch fix-login from=c9d0e1f2 commit "Keep the session after a refresh" checkout main merge fix-login "Pull request #12" id=c5a6b7c8 tag="v1.0.0" commit "Fix a typo in the README" branch "feature/search" from=c5a6b7c8 commit "Add a search box to the header" commit "Index the docs pages" checkout main merge "feature/search" tag="v1.1.0" ``` *Picture: Repository history: 8 commits on 3 branches, 2 merges, 2 tags. 8 commits on 3 branches, with 2 merges and 2 tags, imported from git log.* **Text description** **Branches, in lane order:** - main is the default branch - fix-login was made from main at "Add sign in with email" - feature/search was made from main at "Pull request #12" **History, oldest first:** **1. On main: commit "Initial commit"** **2. On main: commit "Add sign in with email", id c9d0e1f2** - fix-login was made from main at "Add sign in with email" **3. On fix-login: commit "Keep the session after a refresh"** **4. On main: merge of fix-login into main ("Pull request #12"), id c5a6b7c8, tagged v1.0.0** **5. On main: commit "Fix a typo in the README"** - feature/search was made from main at "Pull request #12" **6. On feature/search: commit "Add a search box to the header"** **7. On feature/search: commit "Index the docs pages"** **8. On main: merge of feature/search into main, tagged v1.1.0** ## Limits | Limit | Value | Past it | |---|---|---| | Text on the hosted service | 10 MiB in one call (10,485,760 characters), 200 files | Refused with `I102` and a hint, as data; a request body over 10 MiB is refused with 413 before it is read. | | Text on the local server and the command line | 100 million characters, 20 thousand files | A guard on memory, not a cap a real project meets. | | Diagrams in one answer | 24 | Each grows a little first; past that the answer pages them, and `partsPage` asks for the next page. Nothing is left out. | | Boxes in a Terraform flow | none | A stack of more than 30 boxes also comes back in `parts`. | | Elements of a C4 view from Structurizr | 80, and 160 relationships | Left out past them with `I200`; draw a smaller view with `options.view` and `options.root`. | | Boxes in another flow (Compose, Kubernetes, a workflow) | 40 a picture | Split into `parts` along its frames, with an overview of its groups when it has some, `I204`. | | Tables in a schema | 20 a picture | Split into `parts` by schema name, then by tables that share keys, after an overview of the parts and the foreign keys between them, `I204`. A key to a table of another part ends on a copy of that table. | | Endpoints of an OpenAPI matrix or map | 20 matrix rows, 8 columns, 60 map rows a picture | Split by tag, and columns into pages, all in `parts`, `I204`. | | Rows and columns of another matrix | 20 rows, 8 columns a picture | Split into pages in `parts`, `I204`. | | Places, slices, flows, or tiles of a chart | 60 a picture | Split into pages in `parts`, `I204`; a pie keeps the rest as one slice on each page. | | Series of an XY chart | 6 a picture | Split into groups of series in `parts`, `I204`. | | Rows of a tree | 60 a picture | Split by folder into `parts`, `I204`. | | Commits of a git graph, events of a timeline | 40 a picture | Split into consecutive pages in `parts`, newest first, `I204`. | | Messages of a sequence | 30 a picture | Split into sequences in `parts`, each with the request, `I204`. | Nothing in a file is left out by a picture's size: past it, the import comes back in `parts`, each made a little bigger before there are more. An answer holds 24 parts; `partsTotal` says how many there are and `partsPage` asks for the next 24. A part can still be dense: validation then warns with `W200`. ## Import codes Every import diagnostic points into the file you sent. | Code | Means | What to do | |---|---|---| | `I100` | The text is empty. | Send the content of the file. | | `I101` | The text cannot be read as that format, such as YAML indented with a tab or a quote that never closes. | Fix the line it names, or check that `format` matches the file. | | `I102` | The call is over the limits: characters that are not white space or a comment line, all characters, or files. | Send fewer files or one part, or use the local server, which reads far more. | | `I103` | The importer wrote source that does not parse. | A mistake in the importer, not in your file: report it, and draw by hand meanwhile. | | `I104` | An option names something the file does not have, such as an `operationId`. | Use one the hint lists. | | `I105` | Nothing in the file can be drawn, such as SQL with no `CREATE TABLE`. | Check the format, or that the file holds what it should. | | `I200` | A cap left items out. | Import a smaller part, or accept the summary. | | `I201` | A construct or a file was skipped, such as a file of another format in a folder. | Read the hint; add it by hand if it matters. | | `I202` | A reference points at something the file does not define, such as a foreign key to a missing table. | Include the target in the text, or ignore it if it lives elsewhere. | | `I203` | Something was changed to fit, such as a short CSV row padded with empty cells or a matrix cell that is not yes or no turned into `na`. | Check the row it names, and fix the file if the change is wrong. | | `I204` | A schema has more than six tables, so its text will be small; or an API was split into `parts` by tag. | Draw a schema in parts with `options.tables`, using the groups the hint names, such as `["users", "org_*"]`; show every part of a split API. | | `I206` | Much of the file is not in the picture; `coverage` says how much. | Say so when you show it, read the other diagnostics, and import a smaller part or choose with the options. | ## See also | Doc | Why | |---|---| | [import_source](https://docs.plotwork.dev/mcp/tools/import-source.md) | The tool's arguments, result, and limits | | [Import source over HTTP](https://docs.plotwork.dev/api/post-v1-import.md) | `POST /v1/import` | | [Migrating from Mermaid](https://docs.plotwork.dev/migrating-from-mermaid.md) | The other importer: Mermaid diagrams to Plotwork | | [Writing with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The fix-and-check loop after an import | | [Diagnostic codes](https://docs.plotwork.dev/reference/language/diagnostics.md) | Every code, including the import codes | | [Diagrams as code](https://docs.plotwork.dev/diagrams-as-code.md) | Keep an imported diagram in step with its files in CI | | [Matrices](https://docs.plotwork.dev/matrices.md), [XY charts](https://docs.plotwork.dev/xy-charts.md), [Tree diagrams](https://docs.plotwork.dev/tree-diagrams.md), [Git graphs](https://docs.plotwork.dev/git-graphs.md), [C4 diagrams](https://docs.plotwork.dev/c4-diagrams.md) | The kinds an import writes, to edit the result | --- # How a diagram is drawn > Page: https://docs.plotwork.dev/how-it-works. Index of every page: https://docs.plotwork.dev/llms.txt What happens to a diagram between the text you write and the picture you see: it is checked, laid out, drawn, described, and placed in your document. It is for anyone who wants to know why a diagram looks the way it does, and what to expect when something goes wrong. ## TL;DR | Question | Answer | |---|---| | What do I write? | Plain text, one statement per line. You choose what a thing is (a database, a gateway), never how it looks. | | What if I make a mistake? | Every mistake is reported at once, each with a line, a column, and a hint. Nothing is drawn until the source is valid. | | Who picks the colors? | The theme, in a light and a dark version, so contrast holds in both and diagrams from different authors match. | | Does the picture depend on a font or a browser? | No. The SVG carries its own colors and measured text, so it looks the same on GitHub, in an editor, or as a file. | | Can text overflow a box? | No. Boxes grow to fit their text. Text is cut only past a hard limit, and a warning says so. | | How does it reach my document? | `sync` writes the picture files and a picture block under the fence. | ## 1. The stages **Diagram source** ```diagram flow "From text to picture" desc="Source text is checked, laid out, drawn as SVG, described in words, and written into the document." direction=right node source step "Diagram source" sub="Line-oriented text" node check function "Check" sub="Every mistake, located" node layout function "Layout" sub="Boxes sized to their text" node paint function "Draw" sub="Static SVG" node svg storage "Light and dark SVG" node outline function "Describe" sub="Alt text and description" node sync function "Place" sub="Picture block in the document" node errors terminal "Mistakes" icon=status:error source -> check check -> layout : "valid" check --> errors : "invalid" layout -> paint paint -> svg check -> outline svg -> sync outline -> sync ``` *Picture: From text to picture: 8 nodes, 8 connections. Source text is checked, laid out, drawn as SVG, described in words, and written into the document.* **Text description** **Nodes:** - Diagram source (Line-oriented text), step - Check (Every mistake, located), function - Layout (Boxes sized to their text), function - Draw (Static SVG), function - Light and dark SVG, storage - Describe (Alt text and description), function - Place (Picture block in the document), function - Mistakes, terminal **Connections:** - Diagram source to Check - Check to Layout: valid - Check to Mistakes (dashed): invalid - Layout to Draw - Draw to Light and dark SVG - Check to Describe - Light and dark SVG to Place - Describe to Place ## 2. What each stage does | Stage | What happens | What you see | |---|---|---| | Check | The text is read against the language. Unknown words, missing pieces, and unbalanced blocks are found. | Errors with a line, a column, and a hint, and warnings for a diagram that is too dense or too wide. | | Layout | Each box is sized from the measured width of its text. Flow and schema diagrams route connectors at right angles. Sequence diagrams run on a timeline. | Boxes exactly as large as their text needs, with the same padding on both sides. | | Draw | The layout is painted as SVG using the theme's colors for the light and the dark version. | Two files: `<id>.svg` and `<id>.dark.svg`. | | Describe | The diagram is written out in reading order. | The picture's alt text, and the collapsed "Text description" under it. | | Place | `sync` inserts a picture block under the fence in your Markdown file. | The picture in your document. See [Diagrams in Markdown and on GitHub](https://docs.plotwork.dev/markdown-and-github.md). | ## 3. Why it works this way | Decision | What it means for you | |---|---| | You choose meaning, not color | An agent can write `database` or `gateway` and the look follows. Contrast is the theme's job, in both modes. | | One statement per line, labels in quotes | A mistake has an exact position, and punctuation in a label never breaks the syntax. | | Colors are fixed in the SVG | GitHub shows the picture through an image tag, where page styles and variables do not reach. | | Text is measured, and boxes grow | A long label makes a wider box. A substitute font cannot make text spill out of one. | | Warnings instead of silent fixes | A diagram that is too dense or too wide is a problem for its author, so you are told, with a hint. | | The same source gives the same picture | The result does not depend on the machine that drew it. | | The picture block records a fingerprint | If the diagram or the renderer changes, `sync --check` reports the picture as stale, so a document never shows an old drawing. | ## 4. When something looks wrong | Symptom | Cause | Fix | |---|---|---| | Nothing is drawn and `validate_diagram` lists errors | The source has mistakes | Fix each one using its line, column, and hint. | | A label ends in an ellipsis (warning `W203`) | The text was longer than the hard limit | Shorten it, or move detail into `sub=` or the prose around the diagram. | | The diagram looks small (warning `W202`) | It is wider than a Markdown column | Use `direction=down`, shorten labels, or split it. | | The document shows an old picture | The picture block is stale | Ask your agent to sync the file again, or call `sync_markdown` with it. | ## See also | Doc | Why | |---|---| | [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md) | The language by example | | [Diagrams in Markdown and on GitHub](https://docs.plotwork.dev/markdown-and-github.md) | The sync step in use | | [Language reference](https://docs.plotwork.dev/reference/language.md) | Every word the language accepts | --- # Hosted diagrams > Page: https://docs.plotwork.dev/hosted-diagrams. Index of every page: https://docs.plotwork.dev/llms.txt Publish a diagram and get a public link to its picture for a README, a pull request or a chat. It works over MCP (Model Context Protocol) and over REST. ## TL;DR | Question | Answer | |---|---| | What is a hosted diagram? | The SVG picture of a diagram, stored by Plotwork and served from a public link. Only pictures that Plotwork's own renderer drew are stored. | | How do I publish one? | Ask an agent connected to the hosted MCP server, which calls `publish_diagram`. Or send `POST /v1/diagrams/publish` with an API key. | | Who can see the link? | Anyone who has it. The link holds a long random token that cannot be guessed, and it does not name your account. | | Can I keep a diagram private? | Not with a personal link. Publish to an [organisation](https://docs.plotwork.dev/organisations.md) instead: its diagrams have no public link, and only its members can open them. | | Why did my publish go to an organisation? | An organisation you belong to sends its members' diagrams to its private library. The answer names it and says why in `routedBy`. Send `personal: true` for a public link, unless the organisation requires its library. See [Where members publish](https://docs.plotwork.dev/organisations.md#13-where-members-publish). | | How long is it kept? | On Free, 30 days after you last published it. On Pro, until you delete it. Publishing the same diagram again starts the time over. | | What are the limits? | Free hosts 25 diagrams and 10 MB. Pro hosts 2,000 diagrams and 1 GB. No single diagram may be larger than 2 MB. | | How do I delete one? | Press Delete on the Diagrams page of the portal, or ask an agent connected to the hosted MCP server, which lists your diagrams and calls `delete_diagram` once you name the one to delete. A deleted diagram can stay visible for up to an hour. | | Can I protect a diagram from being deleted? | Yes. Lock it in the portal. A locked diagram cannot be renamed, moved or deleted, by you, an agent or a script, until you unlock it. A lock does not stop the plan's expiry. See [Lock a diagram](#lock-a-diagram). | | Can I edit a diagram from its link later? | Yes, if you published it with `embedSource` set to true. The source is then part of the picture, so anyone who has the link can read it. Without that option the source is not stored. See [Editing an existing diagram](https://docs.plotwork.dev/editing-existing-diagrams.md). | | Does an edit keep the link? | The picture link does not: an edited diagram is a new picture with a new link. A stable link does: name it once, such as `checkout-flow`, publish each edit with that name, and the one address always shows the latest version. See [Links that always show the latest version](#8-links-that-always-show-the-latest-version). | | Can I embed a diagram in Notion or Backstage? | Yes. Paste a stable link or a picture link into Notion as an image or an embed, and use the Backstage card for a service's page. See [Embed a diagram in other tools](#9-embed-a-diagram-in-other-tools). | | Does the local server have this? | No. The local server makes no network requests by default and stores no diagrams, so it has no `publish_diagram` tool. It has no `list_diagrams` or `delete_diagram` tool either. | ## 1. How publishing works | Step | What happens | |---|---| | 1 | You send diagram source, and optionally a theme, a name, and a folder. | | 2 | Plotwork checks the source. Source with mistakes is refused with every mistake, its line, column and hint, as `draw_diagram` does. | | 3 | Plotwork draws the diagram with its own renderer. | | 4 | It stores the SVG and answers with the link. | There is no way to send SVG. The API refuses a field it does not know, so a request that carries an `svg` field fails with status 400 and nothing is stored. A link looks like `https://cdn.plotwork.dev/d/k3T9vQ1xZ0aB7cD2eF4gHw.svg`. It serves the picture and nothing else. Publishing the same source in the same theme again from the same account finds the diagram that is already stored. You get the same link, `created` is false, and the time it is kept starts over. Nothing is stored twice, so an agent that repeats a step does not use up your allowance. If Plotwork's renderer changes, the same source can draw differently and then gets a new link. Each publish is one call toward your [daily limit](https://docs.plotwork.dev/plans-and-billing.md#4-the-daily-call-limit). ### Embedded source By default the diagram source is not stored: the hosted picture shows the text of the diagram, and Plotwork keeps nothing else of it. With `embedSource` set to true, the renderer writes the source into the picture's metadata, and the picture can then be read back by `read_diagram`, by `POST /v1/diagrams/read`, or by anyone who fetches the link. Three things follow: - The source is public. Anyone who has the link can read it, not only see the drawing. Do not set it for a diagram whose source must stay private. - The picture is bigger by about the size of the source, and the plan's size limit counts it. - The same diagram published with and without `embedSource` is two pictures, so it gets two links. Publishing it again with the same choice returns the same link. Nothing sets `embedSource` for you. The [privacy policy](https://docs.plotwork.dev/privacy-policy.md) says the same. To change a diagram from its link, see [Editing an existing diagram](https://docs.plotwork.dev/editing-existing-diagrams.md). ### Folders A diagram can be filed in a folder, such as `Payments/Checkout`. Pass `folder` when you publish, or move a diagram later with Move on the Diagrams page of the portal. A slash nests folders, up to five levels, and each name can be forty characters or fewer. A folder exists while it holds a diagram, so there is nothing to make or delete first. Folders are for your own tidiness: a folder is not part of the link, and nobody who has the link can see it. Publishing the same diagram again with a different folder moves it and keeps its link. Leaving `folder` out keeps the diagram where it is. ## 2. Publish from an agent The hosted MCP server has three tools that the local server does not: `publish_diagram`, `list_diagrams` and `delete_diagram`. This section is about publishing; [List and delete from an agent](#list-and-delete-from-an-agent) covers the other two. [Connect the hosted server](https://docs.plotwork.dev/mcp/connect.md) with an API key or a browser sign-in, then ask in plain words: ```text Publish this diagram and give me the link. ``` The agent calls `publish_diagram` with the source it already has. It is told to publish only when you ask, because anyone who has the link can see the picture. | Argument | Required | Meaning | |---|---|---| | `source` | yes | Diagram source in the line format, the same as for `draw_diagram`. At most 50,000 characters. | | `theme` | no | `light` or `dark`. Light when left out. | | `embedSource` | no | `true` writes the diagram source into the picture so it can be edited later with `read_diagram`. Off when left out. The source is then readable by anyone who has the link, so an agent sets it only when someone may want to edit the diagram, and the answer says it was set. See [Embedded source](#embedded-source). | | `name` | no | A label of at most 80 characters. It appears in your list and is the alt text of the Markdown image. | | `folder` | no | A folder to file the diagram in, such as `Payments/Checkout`. See [Folders](#folders). | | `alias` | no | The name of a stable link, such as `checkout-flow`, which then shows this diagram and every later one published with the same name. Not with `organization`. See [Links that always show the latest version](#8-links-that-always-show-the-latest-version). | | `organization` | no | The handle or id of an organisation. The diagram goes to that organisation's private library instead of a public link. An agent sets it only when you name an organisation. See [Organisations](https://docs.plotwork.dev/organisations.md#7-publish-from-an-agent). | The result starts with the link, then a Markdown image to paste, such as ``. It carries the same fields as the REST answer below, including `locked` and `lockedAt`, and adds `markdown`. When the diagram is locked, the result also says so and that only the time it is kept was refreshed. A refusal says which limit was reached, lists every mistake in the source, or says the diagram is locked. With `organization`, the answer has no public link and no Markdown image, because an organisation diagram has none. It has `private: true` and a `viewUrl`, a page of the portal that needs a sign-in, and the same `id`, `name`, `folder`, `bytes` and `expiresAt`. Your own library's answer does not change. With `alias`, the answer also has `stableLink` with the link's `alias` and `url`, and the Markdown image uses the stable link, so the image you paste keeps showing the latest version. With `embedSource`, the answer also has `sourceEmbedded: true` and a line saying the source is part of the picture. Without it the answer is what it always was. Every other tool of the hosted server only reads, except `delete_diagram`. `publish_diagram` stores a file, so your client may ask for your approval before it runs. ## 3. Publish over REST Send the source in a JSON body, with your API key in the `X-API-Key` header. The command reads the key from the environment variable `PLOTWORK_API_KEY`, so replace nothing in it. ```sh curl -s -X POST https://api.plotwork.dev/v1/diagrams/publish \ -H "X-API-Key: $PLOTWORK_API_KEY" \ -H "content-type: application/json" \ -d '{"source":"flow \"Login\"\nnode user person \"User\"\nnode api service \"Auth API\"\nuser -> api : \"POST /login\"","theme":"light","name":"Login flow"}' ``` A new diagram answers 201. One that was already stored answers 200 with the same link. Add `"embedSource":true` to the body to write the source into the picture. ```json { "id": "0b6f9a52-4d1e-4c7a-9f3e-6d2a1b8c5e70", "url": "https://cdn.plotwork.dev/d/k3T9vQ1xZ0aB7cD2eF4gHw.svg", "name": "Login flow", "theme": "light", "bytes": 8412, "createdAt": 1791515700000, "publishedAt": 1791515700000, "expiresAt": 1794107700000, "created": true, "locked": false, "lockedAt": null } ``` | Field | Type | Meaning | |---|---|---| | `id` | string | The handle of the diagram in your list. It is not part of the link. | | `url` | string | The public link to the SVG. | | `name` | string or null | The label you gave, or null. | | `theme` | `light` or `dark` | The color scheme it was drawn in. | | `bytes` | integer | Size of the stored SVG. | | `createdAt` | integer | When it was first published, in milliseconds since the Unix epoch. | | `publishedAt` | integer | When it was last published. Publishing it again moves this. | | `expiresAt` | integer or null | When it will be deleted, or null on a plan that keeps diagrams until you delete them. | | `created` | boolean | True when this call stored a new diagram. False when it was already there. | | `locked` | boolean | True while you have the diagram locked. See [Lock a diagram](#lock-a-diagram). | | `lockedAt` | integer or null | When it was locked, in milliseconds since the Unix epoch, or null when it is not locked. | These are the failures a client should expect. Every one uses the [error envelope](https://docs.plotwork.dev/api/reference.md#error-envelope), and the `errorCode` is the field to branch on. | Status | `errorCode` | What it means and what to do | |---|---|---| | 400 | `validation.failed` | The body did not match: `source` is missing, a field is unknown, `name` is longer than 80 characters, or `alias` is not lower-case words joined by hyphens. Fix the body. | | 401 | `auth.missing` or `auth.invalid` | There is no API key, or it is wrong or revoked. Create a key in the portal. | | 403 | `hosting.quota_exceeded` | The plan holds no more diagrams, or no more stable links. `details` says `reason` (`count`, `bytes` or `stableLinks`), the `plan`, the `limit` and what is `used`. Delete a diagram or a stable link, or upgrade. | | 413 | `hosting.too_large` | The diagram draws larger than the plan hosts. `details` has its `bytes` and the `limit`. Simplify the diagram. | | 422 | `diagram.invalid` | The source has mistakes. `details` lists each with its line, column and hint. | | 429 | `usage.daily_limit` or `rate_limited` | The daily limit is spent, or the per-minute limit is. See [the daily call limit](https://docs.plotwork.dev/plans-and-billing.md#4-the-daily-call-limit). | | 502 | `upstream` | The diagram could not be stored. Try again. | | 503 | `hosting.unavailable` | Hosting is not set up on this server. | The [publish endpoint page](https://docs.plotwork.dev/api/post-v1-diagrams-publish.md) lists the request and the answer as the API describes them. ## 4. Limits The limits are per account. They are the same numbers the API holds your account to. | Limit | Free | Pro | |---|---|---| | Hosted diagrams | 25 | 2,000 | | Hosted storage | 10 MB | 1 GB | | Largest hosted diagram | 2 MB | 2 MB | | Kept after you last publish | 30 days | Until you delete it | | Links that always show the latest version | 3 | 500 | A refusal for a full allowance is checked at the moment you publish, so two publishes that arrive together cannot both pass. [Plans and billing](https://docs.plotwork.dev/plans-and-billing.md) covers the other limits, and the Diagrams page of the portal shows how much of your allowance you have used. ## 5. How long a diagram is kept | Plan | What happens | |---|---| | Free | A diagram is deleted 30 days after you last published it. Publishing the same diagram again moves that date out by a full 30 days. | | Pro | A diagram stays until you delete it, and `expiresAt` is null. | | You upgrade | Every diagram you already host stops expiring at once. | | You move to Free | Nothing is deleted at once. Each diagram that had no expiry gets a full 30 days from the change. | An expired diagram is removed by a clean-up that runs once a day, so a link can keep working a little past its expiry date. ## 6. Who can see a link Anyone who has the link can see the picture. Treat it as you would a public address: do not publish a diagram that must stay private, and do not paste the link where you would not paste the diagram itself. The link cannot be guessed, and it does not name your account. The picture itself carries the diagram's id in a `data-plotwork-id` attribute on its root element. It is the same id the Diagrams page shows with Copy ID, and the one the API lists, moves and deletes a diagram by, so a copy of the file can be traced back to its diagram. The id is not your account id and it is not a secret: using it takes you, signed in, and for anyone else the API answers as if the diagram did not exist. Only you can list your diagrams: from the portal while you are signed in, or through an agent connected to your account with your key or your sign-in. The picture holds the text of the diagram, such as its node labels, so check what a diagram says before you share it. A diagram published to an [organisation](https://docs.plotwork.dev/organisations.md) is different. It has no public link, and only the organisation's signed-in members, and API keys that belong to the organisation or to a member, can open it. ## 7. Delete a diagram ### Delete in the portal 1. Sign in at [app.plotwork.dev](https://app.plotwork.dev) and open the Diagrams page. 2. Press Delete on the diagram, then confirm. The link stops working. Anyone who has it sees a missing picture, and so does any README or page that embeds it. A copy can stay visible for up to an hour, because the picture is cached and Plotwork does not clear that cache for each delete. The Diagrams page also lists every diagram with its size, when it was created, when it expires, and buttons that copy its link or its Markdown. Deleting over REST needs your signed-in session, so a REST call with an API key cannot delete a diagram. An agent connected to the hosted MCP server can, with the tool described next, and only after you have named the diagram. ### List and delete from an agent [Connect the hosted server](https://docs.plotwork.dev/mcp/connect.md) with an API key or a browser sign-in, then ask in plain words: ```text Show me the diagrams I have published. ``` The agent calls `list_diagrams`, which reads and changes nothing, and tells you what is stored. Then name the one to remove: ```text Delete the Login flow diagram. ``` The agent calls `delete_diagram` with the id it got from the list. It deletes only the diagram you named, and tells you which one it deleted. | Tool | Argument | Required | Meaning | |---|---|---|---| | `list_diagrams` | `organization` | no | The handle or id of an organisation, to list its private library. An agent sets it only when you name an organisation. Left out, your own library is listed. | | `list_diagrams` | `folder` | no | List only the diagrams filed in this folder and the folders inside it, such as `Payments/Checkout`. | | `list_diagrams` | `limit` | no | How many to list, from 1 to 50. The default is 20. | | `delete_diagram` | `id` | yes | The id of the diagram, exactly as `list_diagrams` gave it. It is the same id that Copy ID shows on the Diagrams page. | | `delete_diagram` | `organization` | no | The handle or id of the organisation whose library holds the diagram. Left out, your own library is used. | | `delete_diagram` | `confirm` | yes | Must be `true`. The agent sets it only after you have said, in the conversation, to delete that diagram by name. | The result of `list_diagrams` says whether the library is yours or an organisation's, how many diagrams it holds in all, and where the account stands against its plan. It lists each diagram with its `id`, `name`, `folder`, `bytes`, `createdAt`, `publishedAt` and `expiresAt`, whether the owner locked it (`locked` and `lockedAt`), and whether you may delete it (`canDelete`). A diagram in your own library has its public `url`; one in an organisation has its `viewUrl` and no public link. The result of `delete_diagram` has the `id`, `deleted: true`, `private` (true for an organisation's library) and how many diagrams `remaining` in that library. | Question | Answer | |---|---| | When does an agent delete a diagram? | Only after you have said, in the conversation, which diagram to delete, by name. The tool tells the agent never to delete on a guess, in bulk, or to make room for a new diagram unless you asked. | | What happens if my library is full? | A publish is refused with `hosting.quota_exceeded`. The agent tells you, and offers three choices: delete a diagram you name, delete diagrams in the portal, or upgrade. It does not delete anything on its own to make room. | | What does `confirm` do? | It must be `true`, and a call without it is refused before anything is deleted. It is a check that the agent meant the call, in addition to your client's own approval for a tool that destroys something. | | Who may delete a diagram? | In your own library, you. In an organisation, the member who published it, an admin and the owner, and no reader. See [Organisations](https://docs.plotwork.dev/organisations.md#who-may-delete-and-lock). Plotwork decides, not the agent. | | Can I undo a delete? | No. The diagram, its file and its link are gone. Publish the diagram again to get a new picture and a new link. | | What if I ask twice? | The second call finds nothing to delete and is refused as not found. | | Can an agent lock or unlock a diagram? | No. A lock is set and lifted by you, in the portal. | ### Lock a diagram A lock protects a diagram you want to keep. Lock it in the portal, and it cannot be renamed, moved or deleted, by you, by an agent acting for you, or by a script, until you unlock it. A delete is refused with `hosting.diagram_locked`, and the agent is told that nobody can delete the diagram until its owner unlocks it, and not to work around the lock. `list_diagrams` shows a locked diagram as `locked`. | Question | Answer | |---|---| | Who can lock or unlock a diagram in my own library? | You, in the portal. An agent and an API key cannot. | | Does publishing the same diagram again still work? | Yes, when you give it no new name and no new folder, because that changes nothing a lock protects. A publish that would rename the diagram or file it in another folder is refused. | | Does a lock keep a diagram past its expiry? | No. A lock does not stop the plan's expiry, so a diagram that expires is still removed when its time is up. See [How long a diagram is kept](#5-how-long-a-diagram-is-kept). | | Does a lock keep my account from being deleted? | No. Deleting your account deletes your diagrams, locked or not. | ## 8. Links that always show the latest version A picture link is per drawing: change the diagram and publish it, and you get a new picture at a new link. A stable link is a name you choose, such as `checkout-flow`, with an address of its own that always shows the diagram you last published under that name. Paste the stable link into a README, a wiki or a Notion page once, and every later version shows there by itself. | Question | Answer | |---|---| | How do I make one? | Publish with `alias` set to a name: `"alias": "checkout-flow"` in the REST body, or ask an agent to publish with that alias. The answer has a `stableLink` with its `url`. In the portal, open Share on a diagram and point a link at it by name. | | How do I update what it shows? | Publish the edited diagram with the same `alias`. The link moves to the new picture and its address does not change. Pages that embed it show the new version within a minute. | | What does the address look like? | `https://cdn.plotwork.dev/l/q3Zr8J1x0bWcVvKm2hT9pA.svg`, beside the pictures. The part before `.svg` is a random token made for the link. It does not hold the name you chose, your account, or the token of any picture, so the address cannot be guessed and says nothing about you. | | What does opening it do? | It serves the picture the link shows now, as an SVG, with no redirect: Plotwork keeps a copy of that picture at the link's address and replaces it when you move the link. The copy may be cached for 60 seconds. An image tag, a README, Notion and curl get the SVG. A browser that opens it as a page shows the picture itself, not the [viewer](https://docs.plotwork.dev/viewing-diagrams.md) a picture link opens. | | Does the copy count against my storage? | No. It repeats a picture you already host and that is already counted. | | What names are allowed? | Lower-case letters and digits, in words joined by single hyphens, up to 60 characters. A name that breaks the rule is refused, never changed. Each name is yours alone: another account can use the same name for its own link. | | Who can see the name? | Only you. It is listed in your Diagrams, in the Share box and by `list_diagrams`, and is not part of the address or the picture. | | What if the diagram it shows is deleted or expires? | The link stays, its copy is removed, and its address answers "not found" until you publish with the name again, which brings the same address back, or delete the link. An expired diagram's copy is removed by the daily clean-up, like the picture. | | How do I delete one? | Press Remove beside it in the Share box, or call `DELETE /user/stable-links/{id}` with your session. The copy is deleted, and its address stops working once a cached copy runs out, within a minute. The diagram is not touched. | | How many can I keep? | Free keeps 3 and Pro keeps 500. Moving a link you already have never counts again. A new name past the limit is refused with `hosting.quota_exceeded` and `reason` `stableLinks`, and nothing is published. | | Do organisation diagrams have them? | No. An organisation's diagrams have no public address at all, so there is nothing for a stable link to point at. Share the diagram's page in the portal with members instead. | Publish over REST with an alias: ```sh curl -s -X POST https://api.plotwork.dev/v1/diagrams/publish \ -H "X-API-Key: $PLOTWORK_API_KEY" \ -H "content-type: application/json" \ -d '{"source":"flow \"Checkout\"\nnode cart service \"Cart\"\nnode pay service \"Payments\"\ncart -> pay","alias":"checkout-flow"}' ``` The answer is the usual one with a `stableLink` added: ```json { "stableLink": { "id": "8f3c2a10-5b7e-4d21-9c64-1a2b3c4d5e6f", "alias": "checkout-flow", "url": "https://cdn.plotwork.dev/l/q3Zr8J1x0bWcVvKm2hT9pA.svg", "diagramId": "0b6f9a52-4d1e-4c7a-9f3e-6d2a1b8c5e70", "createdAt": 1791515700000, "updatedAt": 1791515700000 } } ``` From an agent, ask for the name in plain words, and ask for the same name every time you update the diagram: ```text Publish this diagram with the alias checkout-flow and give me the stable link. ``` `GET /user/diagrams` lists every stable link you keep under `stableLinks`, with the diagram each one shows, and `list_diagrams` shows each diagram's stable links beside its picture link. ## 9. Embed a diagram in other tools | Tool | How | |---|---| | Notion | Type `/image`, choose Embed link, and paste a stable link or a picture link. Notion shows the SVG, and a stable link shows each new version the next time the page loads. Pasting the link on its own line and choosing Embed works too. | | Slack, Discord, wikis | Paste the link. A tool that reads oEmbed asks Plotwork's oEmbed endpoint (below) and shows the picture; one that does not shows the link, and an image block that takes an address shows the picture. | | Backstage | In TechDocs, use the Markdown image with a stable link, as in a README. For a card on a service's page, Plotwork's Backstage card shows a stable link, a picture link, or an SVG committed in the service's repository, named in the service's `plotwork.dev/diagrams` annotation. The card is not published as a package yet. | | GitHub, a README | Use the Markdown image the publish answer gives, with the stable link in it when you named one. | The oEmbed endpoint is `GET https://api.plotwork.dev/oembed?url=<link>&format=json`. It takes a stable link, a picture link, or a viewer page, and answers a `photo` with the picture's address, its `width` and `height` (scaled down to `maxwidth` and `maxheight` when you send them), the title "Diagram", and no author: a diagram's name is shown only to you. For a stable link the picture's address is the stable link itself, so an embed made from it keeps showing the latest version. Only `format=json` is answered, and a link that is not a Plotwork diagram, or shows nothing now, is a 404. The viewer page names the endpoint in its head, for tools that look there. ## See also | Doc | Why | |---|---| | [Editing an existing diagram](https://docs.plotwork.dev/editing-existing-diagrams.md) | Read the source back from a link or a file, change it, and publish again | | [Organisations](https://docs.plotwork.dev/organisations.md) | Share a private library of diagrams with a team, with no public link, and who may delete or lock a diagram there | | [Plans and billing](https://docs.plotwork.dev/plans-and-billing.md) | What Free and Pro include, the daily limit and how to upgrade | | [Connecting an agent](https://docs.plotwork.dev/mcp/connect.md) | How to connect the hosted server that has `publish_diagram`, `list_diagrams` and `delete_diagram` | | [MCP server](https://docs.plotwork.dev/mcp/overview.md) | Which tools the local and the hosted server offer | | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Publish a diagram and get a link](https://docs.plotwork.dev/api/post-v1-diagrams-publish.md) | The publish endpoint as the API describes it | | [Point a stable link at one of your diagrams](https://docs.plotwork.dev/api/post-user-stable-links.md) | Make or move a stable link from your session | | [Describe a diagram link for an embedder](https://docs.plotwork.dev/api/get-oembed.md) | The oEmbed endpoint | | [Slack, email, Confluence, and Notion](https://docs.plotwork.dev/wikis-and-chat.md) | A PNG for a chat, and paste-ready text for a wiki | --- # Organisations > Page: https://docs.plotwork.dev/organisations. Index of every page: https://docs.plotwork.dev/llms.txt Share a private library of diagrams with a team. An organisation is a named group of accounts. Its members publish to one library that only they can open, by hand in the portal or from an agent. ## TL;DR | Question | Answer | |---|---| | What is an organisation? | A named group of accounts with one private library of diagrams. You can own some and belong to others. It works on the Free plan and the Pro plan. | | Whose plan does it use? | Its owner's. A Pro owner gives the whole organisation the Pro limits, and a Free owner gives the Free ones. A member's own plan never matters, so the people you invite need no plan. | | Who can see a diagram? | Only members, after they sign in, and API keys that belong to the organisation or to a member. An organisation diagram has no public link. | | What are the roles? | Owner, admin, member and reader. A reader can look and download but change nothing. See [Roles](#3-roles). | | Can I keep a folder to some people? | Yes. The owner can restrict a folder to some roles and some named members. See [Restrict a folder](#6-restrict-a-folder). | | How do people join? | An owner or admin invites an email address. The person opens the link in the email, signs in with that address, and accepts. An invitation works for seven days. | | Can an agent use it? | Yes. Pass the `organization` argument to `publish_diagram`, `list_diagrams` or `delete_diagram` on the hosted MCP server, or use an organisation API key. An agent deletes a diagram only after you have named it. | | Who can delete a diagram? | The member who published it, an admin and the owner. A reader cannot. See [Who may delete and lock](#who-may-delete-and-lock). | | Can we keep members' diagrams off public links? | Yes. The owner or an admin sets **Publishing**: members' diagrams go to the library by default, or, on Pro and Enterprise, always. See [Where members publish](#13-where-members-publish). | | Can I protect a diagram from being deleted? | Yes. The owner can lock it. Nobody can then rename, move or delete it, the owner included, until the owner unlocks it. A lock does not stop the plan's expiry. | | Can I show an organisation diagram in a README? | No, because there is no public link to put there. Download the SVG and commit it, or publish the diagram to your own library when it may be public. | | How do I delete an organisation? | The owner deletes it in its Settings tab. Every diagram in it goes too. | | Can an organisation have a picture? | Yes, on every plan. Each one shows a mark made from it, and the owner or an admin can upload an SVG, PNG, GIF or WebP, still or animated. See [The avatar](#11-the-avatar). | | Can I change a diagram in the browser? | Yes, when its picture carries its source. Choose Edit: the source and the live picture sit side by side, and Save replaces the diagram. Saving is on Pro. See [Edit a diagram in the browser](#12-edit-a-diagram-in-the-browser). | ## 1. What an organisation is An organisation holds a library of diagrams for a team. A person who makes one is its owner, and the owner invites the others. Plotwork keeps an organisation's diagrams apart from every member's own hosted diagrams, so joining a team never mixes its library with yours. | | Your own library | An organisation's library | |---|---|---| | Who can open a diagram | Anyone who has its link | Members who are signed in, and keys that belong to the organisation or to a member | | How a diagram is addressed | A public link on `cdn.plotwork.dev` | A page of the portal that needs a sign-in | | Where the file is stored | Public storage, served from its link | Private storage with no public address, read only through the API | | What it counts against | Your plan | The organisation's own pool, against its owner's plan | | Who can delete a diagram | You, unless you locked it | The member who published it, an admin, or the owner, unless the owner locked it | An organisation has a name and a handle. You can rename the organisation, but the handle never changes. | Part | Rule | |---|---| | Name | 1 to 60 characters. This is what people read. | | Handle | 3 to 40 characters: lowercase letters, digits and hyphens, with no hyphen at either end. You choose it when you make the organisation. If you leave it out, Plotwork makes one from the name and adds a few characters when that handle is taken. The portal address uses it, and so does the `organization` option of `publish_diagram`. | There is no public page for an organisation and no way to search for one or for a person. The handle is a name for addresses and for agents, not a listing. ### Names and the word filter A name, and a handle you choose, must not hold a word from a list of English profanities and slurs. The filter reads through capital letters, look-alike characters, numbers used for letters, repeated letters, and a word spaced out one letter at a time. It lets through ordinary words that only contain one, such as the name of a town. A refused name is answered with `orgs.name_not_allowed` (status 422), which never says which word. The check runs when you make the organisation and when you rename it, before anything is saved. It is a word list, a filter for the obvious and not a moderator, so it decides nothing about meaning. ## 2. Plans and limits An organisation has the plan of its owner. The owner is the account that made it. A Pro owner gives the organisation the Pro limits for people and for diagrams, and a Free owner gives it the Free ones. A member who belongs to a Pro organisation gets nothing for their own account from it, and a member's own plan changes nothing for the organisation. These are the numbers the API holds each organisation to. The hosted rows apply to each organisation on its own, not to the owner's personal diagrams. | Limit | Free | Pro | Enterprise | |---|---|---|---| | Organisations you can own | 1 | 5 | Custom | | People in one organisation (Owner, readers and pending invitations included) | 3 | 25 | Custom | | Brand themes (The organisation's colors on every diagram the hosted service draws) | Not included | Included | Included | | Saved style rules (The same rules for CI in every repository; a rules section in your own settings file is free) | Not included | Included | Included | | Members publish only to the library (No member's diagram at a public link; sending them to the library by default is on every plan) | Not included | Included | Included | | Hosted diagrams | 25 | 2,000 | Custom | | Storage | 10 MB | 1 GB | Custom | | Largest diagram | 2 MB | 2 MB | Custom | | Kept after you last publish | 30 days | Until you delete it | Custom | | Rule | What it means | |---|---| | Organisations you can own | Only organisations you made count. An organisation you belong to does not. | | People in one organisation | The owner, every member (readers too, because the limit is on people), and every invitation that has not been accepted all count. A full organisation cannot invite anyone until a member leaves or an invitation is revoked or expires. | | Diagrams and storage | Counted for the organisation. When a publish would pass the limit, it is refused with `hosting.quota_exceeded`, and the numbers in the refusal are the organisation's. | | How long a diagram is kept | The owner's plan decides. A Free owner's organisation keeps a diagram for the number of days in the table after it was last published. A Pro owner's keeps it until it is deleted. Publishing the same diagram again starts the time over. | | Daily calls | There is one allowance for each account. A call made with an organisation key counts toward the owner's allowance, so a team on Free shares the owner's. A call made with a member's own key counts toward that member's. See [the daily call limit](https://docs.plotwork.dev/plans-and-billing.md#4-the-daily-call-limit). | When the owner's plan changes, the organisation follows it, as the owner's own diagrams do. Moving to Pro stops every expiry in the organisation's library at once, and moving to Free gives each diagram that had no expiry a full period from the change, the number of days in the table above. Nothing is deleted at once. [Plans and billing](https://docs.plotwork.dev/plans-and-billing.md) shows how to upgrade. ## 3. Roles Every member has one role. The owner is the account that made the organisation, and there is exactly one. A reader is for a person who may look and may not change anything, such as a stakeholder, an auditor or a contractor. A reader counts toward the limit on people like any other member. | Action | Owner | Admin | Member | Reader | |---|---|---|---|---| | See the organisation, its members and its diagrams, and download a diagram | Yes | Yes | Yes | Yes | | Publish a diagram | Yes | Yes | Yes | No | | Move, rename or delete a diagram | Yes | Yes | Their own only | No | | Lock and unlock a diagram | Yes | No | No | No | | Invite someone, and revoke an invitation | Yes | Yes, as member or reader only | No | No | | Remove a member | Yes | Yes, members and readers, not admins | No | No | | Change a member's role | Yes | No | No | No | | Rename the organisation | Yes | Yes | No | No | | Make, list and revoke the organisation's API keys | Yes | Yes | No | No | | See the folder rules | Yes | Yes | No | No | | Make and remove a folder rule | Yes | No | No | No | | Save, preview and remove the brand theme | Yes | Yes | No | No | | Save and remove the style rules | Yes | Yes | No | No | | Upload, replace and delete custom icons | Yes | Yes | No | No | | Upload, replace and remove the avatar | Yes | Yes | No | No | | Keep diagrams editable | Yes | No | No | No | | Choose where members publish, and move or unpublish their public diagrams | Yes | Yes | No | No | | Read and export the audit log | Yes | Yes | No | No | | Set up single sign-on | Yes | Yes | No | No | | Send audit events and drift to your tools | Yes | Yes | No | No | | Leave | No, delete the organisation instead | Yes | Yes | Yes | | Delete the organisation | Yes | No | No | No | A restricted folder can narrow this table for a folder (see [Restrict a folder](#6-restrict-a-folder)), and never widens it: a rule that names a reader lets them see the folder and no more. "Their own" means a diagram the member published. A diagram whose publisher has left, or whose account was deleted, can be changed only by an admin or the owner. The owner cannot be removed or demoted, so an organisation never has no owner. Ownership cannot be transferred yet, because it decides the plan. ### Who may delete and lock | Question | Answer | |---|---| | Who may delete a diagram? | The member who published it, an admin and the owner. A member cannot delete a teammate's diagram, and a reader cannot delete any. An organisation API key deletes as an admin does. Plotwork checks the caller on every delete, whether it comes from the portal, from a script or from an agent, so nothing a caller says can widen it. | | Who may lock a diagram? | Only the owner, in the portal. An admin cannot lock or unlock, and an organisation key never does. | | What does a lock do? | A locked diagram cannot be renamed, moved or deleted by anyone, the owner and the admins included, until the owner unlocks it. Everyone who could see it still can, and can still download it. | | How does a locked diagram look? | The portal shows it as locked. An agent's `list_diagrams` shows it as `locked`, and a delete is refused with `hosting.diagram_locked`. | | Does a lock change publishing? | Publishing the same diagram again with no new name and no new folder still works, because it changes nothing a lock protects. A publish that would rename the diagram or file it in another folder is refused. | | Does a lock keep a diagram past its expiry? | No. A lock does not stop the plan's expiry, so a diagram on a Free owner's organisation is still removed when its time is up. | | Does a lock keep the organisation from being deleted? | No. Deleting the organisation deletes every diagram in it, locked or not. | ## 4. Make an organisation and invite people ### Make an organisation 1. Sign in at [app.plotwork.dev](https://app.plotwork.dev) and open the Organisations page. 2. Enter a name, and a handle if you want to choose it. 3. Choose to make the organisation. You are its owner. If you already own as many organisations as your plan allows, the form says so and points to the Plan page. The API answers `orgs.limit_reached`. If the handle is taken, it answers `orgs.slug_taken`. ### Invite a person 1. Open the organisation and the Members tab. 2. Enter the person's email address, choose a role (admin, member or reader), and send the invitation. 3. The address gets an email that names who invited them, the organisation and the role, with a link. 4. The person opens the link, signs in with that same address, and accepts. | Question | Answer | |---|---| | Who can invite? | The owner, and admins. An admin can invite only as a member or a reader. Nobody is invited as the owner. | | How long does an invitation work? | Seven days. After that it is deleted, and an admin sends a new one. | | Can someone else use a forwarded link? | No. The person must sign in with the address that was invited, so a forwarded link is no use to anyone else. If they sign in with another address, the page says which address was invited and offers to sign in as another. | | What does a link do? | It is used once. Accepting makes the person a member and deletes the invitation. | | Can I take an invitation back? | Yes. An owner or admin revokes it from the Members tab, and the link stops working. | | What if the organisation is full? | An invitation counts toward the limit on people the moment it is sent. If the organisation fills before the person accepts, accepting is refused with `orgs.member_limit_reached`. | | Is there one invitation for each address? | Yes. Inviting an address that already has a pending invitation is refused with `orgs.already_invited`, and an address that is already a member with `orgs.already_member`. | ### Leave or remove a member | You do this | What happens | |---|---| | Leave | An admin, a member or a reader chooses Leave on the Members tab. Their member row goes. The diagrams they published stay, and no longer name them as the publisher. | | Remove a member | An owner can remove anyone but themselves. An admin can remove members. Removing does not delete the member's diagrams, which no longer name the member as the publisher. | | Change a role | The owner chooses admin, member or reader for any other member. Only the owner changes a role. | ## 5. The private library Every member sees the organisation's library on its Diagrams tab: a folder tree and a list, a preview of each diagram, and where the organisation stands against its limits. From there a member can download a diagram, and can publish, move, rename or delete the diagrams their role lets them change (see [Roles](#3-roles)); a reader can only look and download. Each diagram has a page of its own that shows it large, with the same actions. That page needs a sign-in, so it is never a link that anyone can open. Folders work as they do for your own library: a slash nests folders up to five levels, each name can be forty characters or fewer, and a folder exists while it holds a diagram. See [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md#folders). ### Private means no public link An organisation's files are stored in a private place that has no public address. The API reads them and serves them only to a signed-in member, or to an API key that belongs to the organisation or to a member. Every answer says not to keep a copy in a shared cache, and a page on another site cannot embed it. That has one practical result: **you cannot put an organisation diagram in a README, a pull request or a web page by its address.** A person who is not signed in would see a broken picture, and a README cannot sign anyone in. You have two ways to show the picture: | Way | When to use it | |---|---| | Download the SVG and commit it next to the README | The picture belongs in the repository, and the repository is as private as the organisation or less so on purpose. | | Publish the diagram to your own library | The picture may be public. It gets a public link, and it is then a personal hosted diagram that anyone who has the link can see. See [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md). | ### Download a diagram A member downloads a diagram with Download in the portal. The file is named after the diagram, with `.svg` at the end. Code that holds a key can read it from `GET /orgs/{org}/diagrams/{id}/svg`, and adding `?download=1` makes the answer a file download. ### How long a diagram is kept The owner's plan decides. On a Free owner's organisation a diagram is deleted the number of days in the table above after it was last published, and publishing it again starts the time over. On a Pro owner's organisation it stays until someone deletes it. A clean-up that runs once a day removes expired diagrams, and the same clean-up deletes invitations that have run out. Publishing the same source in the same theme to the same organisation finds the diagram that is already there, so an agent that repeats a step stores nothing twice. ## 6. Restrict a folder By default every member can use every folder. The owner can restrict a folder to some roles and some people, for example to keep `Payments` to the admins and a few named members. Only the owner makes or removes a rule, in the portal. The owner and admins can read the rules. | Part of a rule | Meaning | |---|---| | Folder | A folder path such as `Payments`. The rule covers that folder and every folder inside it. | | Roles | Any of admin, member, reader and key, which stands for the organisation's API keys. The owner is not a choice, because the owner is always allowed. | | People | Named members, by account. Removing a member, or a member leaving, removes them from every rule. | | Question | Answer | |---|---| | Who can use a restricted folder? | The owner, anyone whose role is named in the rule, and anyone named in it. | | What does an empty rule do? | Only the owner can use the folder. | | Is an admin special? | No. A rule applies to an admin like anyone else, so an admin who is not named cannot see the folder. | | Which rule applies inside a restricted folder? | The nearest one. A rule on a deeper folder replaces the rule above it for that folder and everything in it, and does not add to it. A team can open `Payments/Public` inside a restricted `Payments`. | | Can naming a reader let them change things? | No. A reader named in a rule can see the folder and download from it, and still cannot publish, move, rename or delete. A rule decides who may use a folder. It never gives a role a right the role lacks. | | Do organisation keys need naming? | Yes. A key may use a restricted folder only when the rule names `key` among its roles. A pipeline or an agent that publishes with an organisation key to a restricted folder needs it. | | Does a rule need a diagram? | No. A folder exists only while a diagram is in it, but a rule stays until the owner removes it, so a folder can be restricted before anything is published there. | | How many rules can there be? | A limit on the rules in one organisation and on the people in one rule. Past it, the API answers `orgs.rule_limit_reached`. | This is what a person who is not allowed in a folder sees. | Action | What happens | |---|---| | List the diagrams | The folder's diagrams are left out. The counts of diagrams and storage still include them, because the limits belong to the organisation. | | Open, download, rename, move or delete a diagram in it | The answer is not found (404 `not_found`), the same as for a diagram that does not exist. | | Publish into it, or move a diagram into it | Refused with `orgs.folder_restricted` (403). The person named the folder, so the refusal reveals nothing they did not type. | | Publish a diagram that is already stored in a folder they cannot use | Refused with `orgs.folder_restricted`. A publish never moves or shows a diagram the person may not reach. | | Move a diagram out of it | Not found, as above. Moving a diagram needs access to both folders. | A person who is allowed into a restricted folder sees its diagrams marked as restricted, so they can tell which folders are locked without reading the rule. A rule is a setting of the signed-in portal: its routes take no API key, and they are listed in the [API reference](https://docs.plotwork.dev/api/reference.md). A rule that names a person who is not a member is refused with `orgs.rule_invalid`, and one past the limits with `orgs.rule_limit_reached`. An agent that publishes into a restricted folder is refused with `orgs.folder_restricted`. It should ask the person which folder they may use, and not publish somewhere else. ## 7. Publish from an agent The hosted MCP server's `publish_diagram` tool takes an optional `organization`: the handle or the id of an organisation. [Connect the hosted server](https://docs.plotwork.dev/mcp/connect.md) with an API key or a browser sign-in, then name the organisation in plain words: ```text Publish this diagram to the acme-platform organisation. ``` The agent calls `publish_diagram` with the source it has and `organization` set to `acme-platform`. The diagram goes to that organisation's private library. | Question | Answer | |---|---| | When does an agent use `organization`? | Only when you name an organisation or ask for a team diagram. Without the option, a diagram goes to your own library at a public link, unless an organisation you belong to sends its members' diagrams to its library. See [Where members publish](#13-where-members-publish). | | What comes back? | `private: true`, the diagram's `id`, `viewUrl`, `name`, `folder`, `bytes` and `expiresAt`. There is no public `url` and no Markdown image, because there is no public link. `viewUrl` is a portal page that needs a sign-in. | | Who may publish? | A member, through a browser sign-in or a key of their own, or an organisation key. | | What if the key belongs to an organisation? | With no `organization`, the diagram goes to the key's own organisation, so a CI job set up with one key needs nothing else. A key for one organisation cannot publish to another: the call is refused with `orgs.key_wrong_org`. | | What if I am not a member, or the organisation does not exist? | The call is refused as not found, with the same answer for both. The agent should ask you which organisation you mean, and not try another or fall back to your own library. | | What if the organisation is full? | The refusal is `hosting.quota_exceeded`, with the organisation's numbers. Delete a diagram or ask the owner to upgrade. | | What if the folder is restricted, or I am a reader? | A folder the owner restricted refuses the call with `orgs.folder_restricted`, and a reader cannot publish at all (`orgs.forbidden`). The agent should ask the person which folder they may use, and not publish somewhere else. See [Restrict a folder](#6-restrict-a-folder). | The tool also takes `source`, `theme`, `name` and `folder` exactly as for your own library: see [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md#2-publish-from-an-agent). The local MCP server has no `publish_diagram` and no organisations. There is no MCP tool to read the file of an organisation's diagram. `list_diagrams` lists the diagrams and the portal page of each, but downloading a file is for people, in the portal, and for code that holds a key, over REST. ### List and delete from an agent Two more tools of the hosted MCP server take the same `organization` option: `list_diagrams` and `delete_diagram`. Name the organisation in plain words: ```text Show me the diagrams in the acme-platform organisation. ``` The agent calls `list_diagrams` with `organization` set to `acme-platform`. To remove one, name it: ```text Delete the Login flow diagram from acme-platform. ``` | Question | Answer | |---|---| | What does `list_diagrams` return? | `private: true`, the organisation, and for each diagram its `id`, `name`, `folder`, `bytes`, `createdAt`, `publishedAt`, `expiresAt`, `viewUrl`, whether it is locked (`locked` and `lockedAt`) and whether you may delete it (`canDelete`). It also gives the number of diagrams the organisation holds and where it stands against its plan. There is no public link. A diagram in a folder you may not use is left out. | | When does an agent delete? | Only after you have said, in the conversation, which diagram to delete, by name. The `delete_diagram` tool has a required `confirm` field that must be `true`, and the agent is told to set it only then, and never to delete on a guess, in bulk, or to make room for a new diagram. | | Who is allowed? | Plotwork decides by the role behind the connection, exactly as in [Roles](#3-roles). An agent that uses a member's sign-in or key deletes only what that member may. An agent that uses an organisation key deletes as an admin does. A reader's agent can list and cannot delete. | | What if I am not allowed to delete it? | The call is refused with `orgs.forbidden`. The agent tells you, and does not try other diagrams or another organisation. | | What if the owner locked it? | The call is refused with `hosting.diagram_locked`. The agent tells you that nobody can delete it until the owner unlocks it, and does not try to get around the lock. If you are the owner, it says you can unlock it in the portal. | | What if the organisation, or the diagram, is not found? | The call is refused as not found, with the same answer for an organisation you are not in, one that does not exist, a diagram that is already deleted, and a diagram in a folder you may not use. The agent asks you which one you mean. | | What if the key belongs to another organisation? | The call is refused with `orgs.key_wrong_org`, as for publishing. The agent asks you whether to use the key's own organisation or a key made for the other one. | | Can an agent lock or unlock a diagram? | No. Only the owner locks and unlocks, in the portal. | ## 8. API keys and REST for scripts and CI An organisation has API keys of its own, so a pipeline can publish to the team's library without any person's key. | Question | Answer | |---|---| | Who makes them? | The owner and admins, on the Keys tab. A key is shown once when it is made, and can be revealed again by an owner or admin, as for your own keys. A key with no stored copy cannot be shown again. | | Whose are they? | The organisation's. An organisation key does not appear in your own list of keys, and it keeps working when the member who made it leaves. | | How many? | Up to ten active keys for each organisation. | | What can a key do? | Read the library, publish to it, and move, rename or delete any diagram in it. It never invites, removes, renames, makes keys, or deletes the organisation. It acts only in its own organisation, and in a restricted folder only when the rule names `key`. | | Can a key lock a diagram? | No. Only the owner locks and unlocks, in the portal, and nothing can move, rename or delete a locked diagram until the owner unlocks it, a key included. | | Whose allowance does it spend? | The owner's daily calls. See [Plans and limits](#2-plans-and-limits). | Five routes of the API take a key as well as a sign-in. `{org}` is the organisation's handle or its id. A caller who is not a member, or whose key belongs to another organisation, gets 404, the same answer as for an organisation that does not exist. | Route | What it does | |---|---| | `GET /orgs/{org}/diagrams` | Lists at most 200 diagrams, most recently published first, with the organisation's usage and limits. | | `POST /orgs/{org}/diagrams` | Publishes a diagram: `source`, and optionally `theme`, `name` and `folder`. Answers 201 for a new diagram and 200 for one that was already there, with the diagram and its `viewUrl`. | | `GET /orgs/{org}/diagrams/{id}/svg` | Returns the file. `?download=1` makes it a download. | | `PATCH /orgs/{org}/diagrams/{id}` | Changes `folder` or `name`. A key may change any diagram that is not locked. | | `DELETE /orgs/{org}/diagrams/{id}` | Deletes the diagram, unless it is locked. Unlike your own library, a key may delete here. | The command below publishes from a pipeline. It reads the key from the environment variable `PLOTWORK_API_KEY`, so replace nothing in it except the handle. ```sh curl -s -X POST https://api.plotwork.dev/orgs/acme-platform/diagrams \ -H "X-API-Key: $PLOTWORK_API_KEY" \ -H "content-type: application/json" \ -d '{"source":"flow \"Login\"\nnode user person \"User\"\nnode api service \"Auth API\"\nuser -> api : \"POST /login\"","name":"Login flow","folder":"Payments/Checkout"}' ``` These are the failures a client should expect. Every one uses the [error envelope](https://docs.plotwork.dev/api/reference.md#error-envelope), and the `errorCode` is the field to branch on. | Status | `errorCode` | What it means and what to do | |---|---|---| | 401 | `auth.missing` or `auth.invalid` | There is no key, or it is wrong or revoked. | | 403 | `orgs.forbidden` | The role may not do this. A member may change only their own diagrams, and a reader may change nothing. | | 403 | `orgs.folder_restricted` | The folder is restricted and the caller, or the key, is not allowed in it. A key needs `key` in the rule. | | 403 | `orgs.key_wrong_org` | The key belongs to an organisation, and the call went to the personal publish route. Publish with `POST /orgs/{org}/diagrams` instead. | | 403 | `hosting.quota_exceeded` | The organisation holds no more diagrams or bytes. Delete one, or ask the owner to upgrade. | | 404 | `not_found` | The organisation does not exist, you are not a member, the key belongs to another organisation, or the diagram is in a folder you may not use. The answer is the same for all of them. | | 409 | `hosting.diagram_locked` | The owner locked the diagram, so it cannot be renamed, moved or deleted until the owner unlocks it. `details` has `lockedAt` and `canUnlock`, which is true only for the owner. | | 413 | `hosting.too_large` | The diagram draws larger than the plan hosts. Simplify it. | | 422 | `diagram.invalid` | The source has mistakes. `details` lists each with its line, column and hint. | | 429 | `usage.daily_limit` or `rate_limited` | The owner's daily allowance is spent, or the per-minute limit is. | | 503 | `orgs.unavailable` | Organisations are not set up on this server. | The other organisation routes (creating an organisation, members, invitations, keys) belong to the portal and need a browser sign-in. The [API reference](https://docs.plotwork.dev/api/reference.md) lists every route as the API describes it. ## 9. Delete an organisation Only the owner can delete an organisation, from its Settings tab. | Event | What happens | |---|---| | The owner deletes the organisation | Every diagram is removed from storage first, then the records: diagrams, members, invitations, keys, and the organisation. If removing a file fails, the organisation stays so that you can try again. | | The owner's account is deleted | The organisations it owns are deleted first, as above, then the account. Its memberships elsewhere are removed. Diagrams it published in other organisations stay, without its account id. | | A member leaves or is removed | The member row goes. Their diagrams stay. | | A diagram expires | The daily clean-up removes it, file first. | | An invitation expires | The same clean-up deletes it. | Deleting an organisation cannot be undone, and a lock on a diagram does not stop it. ## 10. Privacy An organisation stores a member row for each person (an account id, a role and a join date), a pending invitation for each address invited (the address, the role and who invited, until it is accepted, revoked, or seven days have passed), and each diagram's drawn picture with its name, folder, size, dates and publisher. A folder rule stores a folder name, which is text you chose, with the roles and the member accounts it names. A lock stores the time it was set and the account id of the owner who set it, and nothing else. A reader is a member and stores the same data as every other member. Members see each other's email addresses, because a team knows who is in it. Where members publish is one setting on the organisation, and a person's default organisation for publishing is one row with their account id and the organisation's id. An uploaded avatar is stored as the check rebuilt it until an owner or admin removes it or the organisation is deleted. Diagram source is still never stored. The audit trail names account ids and organisation ids, including when a rule is set or cleared, and never an email address, a name, a folder or diagram text. The cookie policy does not change, because the portal uses the same session cookie it already used. The [privacy policy](https://docs.plotwork.dev/privacy-policy.md) lists all of it, with why each item is kept and for how long. ## 11. The avatar Every organisation has a picture beside its name in the portal: in its own heading, in the list of organisations, in the account menu, on the dashboard, on a diagram's page, in the share box, in the choice of organisation style, and on the page that says you joined. Its frame has a second rule around it, so an organisation never looks like a person. | Question | Answer | |---|---| | What shows if nobody uploads one? | A small mark made from the organisation, the same everywhere. Nothing is stored for it. | | Who can change it? | The owner and admins, in the Settings tab. Members and readers see it. | | Which plans? | Every plan. | | Which files? | SVG, PNG, GIF or WebP, still or animated. At most 256 KB, 512 pixels on a side, and 200 frames. A square of 256 pixels looks sharp everywhere. | | What is checked? | The same check as [custom icons](https://docs.plotwork.dev/custom-icons.md). An SVG is rebuilt from drawing elements, and for an avatar also from animation that changes its own shapes and starts on a plain time such as `begin="1s"`. Scripts, links, and animation that waits for a click or another element are refused, with a reason for each. A PNG, GIF or WebP keeps only its frames, so text, EXIF and XMP are left out. | | Does it move for everyone? | No. If your device asks for less motion, an animated avatar holds still on its first frame. | | How do I go back to the generated mark? | Choose Remove avatar in the Settings tab. | The preview in the Settings tab shows the picture at every size it is drawn at, on a light and a dark ground, before you upload it. ## 12. Edit a diagram in the browser A diagram can be changed in the portal, for a small fix without asking an agent: choose Edit on its page, in its row's menu, or on the viewer's toolbar. The editor changes the diagram's source, never its picture, and draws the picture again from the source as you type. An agent can go on editing the same diagram afterwards with `read_diagram` and `edit_diagram`. Your own hosted diagrams have the same editor. | Question | Answer | |---|---| | Which diagrams can I edit? | One whose picture carries its source: published with `embedSource` set to true, published while the organisation keeps diagrams editable, or saved in the editor before. Any other picture shows why it cannot be edited and what to do. | | What is Keep diagrams editable? | A switch for the owner in the Settings tab. On, every publish to the organisation that does not set `embedSource` to false writes the source into the private picture. Pictures published before it was on are not changed. | | Who can edit? | Whoever may change the diagram: the owner and admins any diagram, a member their own. A reader cannot. A locked diagram cannot be saved until the owner unlocks it. | | Which plan? | Anyone who may change the diagram can open the editor, edit and preview. Saving needs Pro: the owner's plan for an organisation's diagram, your own for your own. | | Is it sent anywhere while I type? | No. The picture is drawn in your browser with the organisation's brand and icons, so the preview is the picture Save stores. Only Save sends the source. | | What if the source has a mistake? | The mistake is underlined with its hint, listed under the source, and the last picture that drew stays on screen until the source draws again. Save waits until there are no errors. | | How do I save? | When you change something, a bar slides up over the picture with Unsaved changes, Discard and Save. It says Saved for a moment after a save and slides away. Cmd or Ctrl with S and the editor's menu save too, and the dot beside the diagram's name shows where saving stands. Discard puts back the saved version, and Undo brings your changes back. | | What if someone saved while I was editing? | The save bar says so and offers to reload their version or keep yours and replace it. Nothing is lost without you choosing. | | Can I go back? | Undo and Redo work while you edit. Each save keeps the picture it replaced, up to ten, and Restore a previous version in the editor's menu brings one back. They do not count toward the plan's storage, and they are deleted with the diagram. | | Does the link change? | An organisation's diagram keeps its page, name and folder. Your own diagram gets a new public link, because a public picture is one picture; a [stable link](https://docs.plotwork.dev/hosted-diagrams.md#8-links-that-always-show-the-latest-version) keeps its address and shows the new picture. | | Keys | Cmd or Ctrl with S saves, with Z undoes, with Shift and Z redoes. Escape leaves the source. The keys for the picture are in [Edit on the picture](#edit-on-the-picture). | The editor fills the window: the page itself never scrolls, and the source, the inspector and the picture each scroll or pan inside. There is no page footer in the editor. On a phone the source and the picture are two tabs, and the save bar shows on whichever one is open. ### Edit on the picture You can also change the diagram on the picture itself. Every change there is written into the source as the smallest edit that makes it, the same `edit_diagram` operations an agent uses, so typing and editing on the picture share one Undo. Choose a box and its line in the source is chosen too; move the cursor in the source and its box is chosen on the picture. | To | Do this | |---|---| | Choose a box, a line or a group | Click it, or tap it on a phone. Hold Shift, Cmd or Ctrl as you click to add to the choice. | | Change a label | Double click the box, or press Enter. Type, then Enter keeps it, Escape puts it back, and Tab goes on to the next box. | | Change a type, an icon, a tone or a group | The inspector beside the picture shows the fields of what is chosen. On a phone it is a sheet from the bottom, and the picture moves so the box stays in sight above it. | | Connect two boxes | Drag from a handle on a box's edge to another box. Let go on empty paper to add a new box joined to the first. On a phone, hold a finger on a box, then drag. | | Add, duplicate or delete | The toolbar, or the commands (Cmd or Ctrl with K), which find any action and any box by name. A delete shows Undo for a few seconds. | | See more of the picture | Fold the source or the inspector to a thin rail with the arrow button in its header, the toolbar, or the keys below. The editor remembers the choice in this browser. A folded source shows its error count on the rail. | | Make the source wider or narrower | Drag the line between the source and the picture, or focus it and use the arrow keys. A double click puts it back. It only resizes and never folds a pane. | When the picture is drawn again after a change, its boxes glide to their new places. If you have asked your device for less motion, the new picture shows at once. | Key | On the picture | |---|---| | Tab, Shift and Tab | The next or previous box, in the order of the source | | Arrow keys | The nearest box in that direction | | Enter or F2 | Change the chosen box's label | | Delete or Backspace | Delete what is chosen, with its lines | | C | Connect the chosen box to a box you pick from a list | | N | Add a box | | Cmd or Ctrl with D | Duplicate what is chosen | | Cmd or Ctrl with A | Choose every box | | Escape | Let go of the choice, or stop a connection | | Shift and 2, Shift and 1 | Zoom to what is chosen, fit the whole picture | | Plus, minus, 0 | Zoom in, zoom out, fit | | Space, held | Drag the picture from anywhere, even over a box | | Key | Anywhere in the editor | |---|---| | Cmd or Ctrl with K | Open the commands | | Cmd or Ctrl with S | Save | | Cmd or Ctrl with Z, with Shift and Z | Undo, redo | | Cmd or Ctrl with a backslash (`\`) | Fold or open the source | | Cmd or Ctrl with a full stop (`.`) | Fold or open the inspector | ## 13. Where members publish An organisation can decide where its members' diagrams go when they publish with an API key, an agent, the REST API or the GitHub Action. The owner or an admin sets it on the Settings tab, under **Publishing**. | Choice | What a member's publish does | Plans | |---|---|---| | Where each member chooses | Goes to the member's own library at a public link, unless they name this organisation. This is how every organisation starts. | Every plan | | To this library by default | A publish that names no organisation comes to this library. A member can still ask for a public link. | Every plan | | Always to this library | Nothing a member publishes gets a public link while they are a member. A request for a public link, a stable link, or a save over one of their own public diagrams is refused with `orgs.publish_required`, naming the organisation. Publishing to another organisation's private library still works. | Pro and Enterprise | | Question | Answer | |---|---| | Who counts as a member? | Everyone in the organisation, every role, the owner included. With **Always to this library**, also anyone whose email address is on a domain the organisation verified for [single sign-on](https://docs.plotwork.dev/single-sign-on.md), before they join. They cannot publish to the library until they join, so their publishes are refused. | | How does a member ask for a public link? | With `personal: true` on `POST /v1/diagrams/publish` or on the agent's `publish_diagram`, or by asking for a stable link with `alias`. Under **To this library by default** that works; under **Always to this library** it is refused. | | What does the answer say? | A publish the setting sent to the library has `private: true`, the organisation's id, handle and name, `routedBy` (`organisation-default` or `organisation-required`), and a `viewUrl` for members, with no public link. An agent tells you where the diagram went, and the GitHub Action's summary names the library. | | What if I belong to several such organisations? | Choose a default organisation on the Organisations page, under **Where my diagrams go**. Without one, a publish that names no organisation is refused with `orgs.publish_ambiguous`, listing the organisations to choose from. Naming one with `organization` always works. | | What about a reader? | A reader cannot publish to the library. Under **To this library by default** a reader's publishes stay personal; under **Always to this library** they are refused. | | What about an organisation key? | Nothing changes. It publishes only to its own organisation. | | What happens to diagrams members already made public? | Nothing is deleted when the setting changes. Under **Always to this library** the Settings tab lists the public diagrams each member published since they joined, with **Move to library** and **Unpublish**. Moving copies the picture into the library under a new id and removes the public link and the stable links that showed it. Unpublishing removes them without a copy, and cannot be undone. Both ask first, override the member's own lock, and are in the [audit log](https://docs.plotwork.dev/audit-log.md). | | What if the plan stops including Always to this library? | The choice is kept and works as **To this library by default** until the plan includes it again. Nothing becomes public by itself. | ### View links A drawing answer from an agent or the REST API carries a `viewUrl`: an address of the portal's viewer with the diagram's source after `#`. It is not hosted and nothing is stored, because a browser never sends the part after `#` to a server. Anyone who has the address can see the diagram. With **Always to this library** the owner or an admin can also tick **Also hide view links from agents**. The hosted MCP server and the REST drawing routes then leave `viewUrl`, `svgUrl`, `pngUrl` and `saveCommand` out of their answers for members and for the organisation's keys. This keeps an agent from offering a link that looks like a share link by default. It cannot stop a link from being made: the local MCP server, the CLI and anyone's own code build one from the source, which the member already holds. ### What it does not cover An account a member made with an address on a domain nobody verified is not held, because Plotwork cannot know it belongs to an employee. Verify your domains, and remove people who leave, by hand or through SCIM. ## See also | Doc | Why | |---|---| | [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md) | Publish to your own library at a public link, and how folders and keeping work | | [Plans and billing](https://docs.plotwork.dev/plans-and-billing.md) | What Free and Pro include, the daily call limit, and how to upgrade | | [Brand themes](https://docs.plotwork.dev/brand-themes.md) | Paint the organisation's diagrams in its own colors, on Pro | | [Audit log](https://docs.plotwork.dev/audit-log.md) | Who changed what in the organisation, and the export to a SIEM on Enterprise | | [Custom icons](https://docs.plotwork.dev/custom-icons.md) | Upload the company's own logos for its diagrams, checked the way an avatar is | | [Editing existing diagrams](https://docs.plotwork.dev/editing-existing-diagrams.md) | How an agent changes a diagram that already exists, which the browser editor works alongside | | [Connecting an agent](https://docs.plotwork.dev/mcp/connect.md) | Connect the hosted server that has `publish_diagram`, `list_diagrams` and `delete_diagram` | | [MCP server](https://docs.plotwork.dev/mcp/overview.md) | What the tools do, and how the local and the hosted server differ | | [API reference](https://docs.plotwork.dev/api/reference.md) | Every route, and the error envelope | | [Single sign-on](https://docs.plotwork.dev/single-sign-on.md) | Verify the domains whose people a required library holds before they join | | [Privacy policy](https://docs.plotwork.dev/privacy-policy.md) | What is stored about accounts, organisations and diagrams | --- # Brand themes > Page: https://docs.plotwork.dev/brand-themes. Index of every page: https://docs.plotwork.dev/llms.txt Paint every diagram the hosted service draws for your organisation in your company's colors. An owner or admin sets one to three colors in the portal, and the hosted MCP server, the REST API and publishing use them from then on. ## TL;DR | Question | Answer | |---|---| | What is a brand theme? | One to three colors (primary, secondary and accent) saved on an organisation. Plotwork makes a whole palette from them, for the light and the dark picture. | | Which plan? | Pro. The organisation's owner must be on Pro for the brand to be saved and used. The preview works on every plan, so you can see the result before you upgrade. | | Who can set it? | The organisation's owner or an admin, on the organisation's Brand tab in the portal. Members and readers see the saved colors. | | Which calls use it? | The hosted MCP server's drawing tools, the REST API's drawing routes, and publishing, both to your own library and to the organisation's. | | Does the local package use it? | No. The local MCP server, the npm package, the command line and the GitHub Action always draw the built-in theme. Brand themes are part of the hosted service. | | What never changes? | The red, green and amber that mean an error, success and a warning, the text and card colors, and the official AWS, Azure and Google Cloud icons. | | Can an agent choose a brand? | Only among your own organisations. No tool takes colors. Your [repository settings](https://docs.plotwork.dev/repository-settings.md) can name an organisation in `brand.organization`; a hosted call uses its brand when the caller belongs to it. Otherwise the hosted service picks the brand from who is calling. | | What if something goes wrong? | The diagram is drawn in the built-in theme. A brand never stops a drawing. | ## 1. What a brand changes A diagram uses a few tones to tell its parts apart. A brand takes the place of three of them, and lightly tints the frames and lines so the picture reads as yours. | Brand color | What it replaces | If you leave it out | |---|---|---| | Primary | The blue tone, and a light tint in group frames, borders and lines | It is required. | | Secondary | The violet tone | Plotwork makes one by turning the primary's hue, and it follows the primary when you change it. | | Accent | The aqua tone | Plotwork makes one by turning the primary's hue the other way, and it follows the primary too. | Each color keeps a readable contrast on both the light and the dark background. When a color would be too faint on one of them, Plotwork draws a darker or lighter shade of it there, and the portal shows the shade it will use. Some things never change, so a branded diagram still means what it says: | Kept as it is | Why | |---|---| | Red, green and amber | They mean an error, success and a warning in every diagram. A brand color that looks like one of them gets a warning in the portal. | | Text and card colors | Text stays readable on every background. | | Official AWS, Azure and Google Cloud icons | They are drawn the way each vendor publishes them. | | Fonts | Every box is sized from the measured widths of Plotwork's font, so a different font could let text spill out of a box. | ## 2. Set a brand 1. Open the organisation in the portal and choose the Brand tab. 2. Choose a primary color with the picker, or type its hex code, such as `#2a78d6`. Add a secondary and an accent if you want them. 3. Look at the preview. It draws a small flow and a small layered diagram in your colors, in light and dark, and it lists any color that is hard to tell apart or that looks like a status color. 4. Choose Save. To go back to the built-in theme, choose Remove on the same tab. | Role | Can do | |---|---| | Owner | See, preview, save and remove the brand | | Admin | See, preview, save and remove the brand | | Member | See the saved colors | | Reader | See the saved colors | An owner on Free sees the preview and an upgrade link in place of Save. If the owner moves from Pro to Free, the saved colors are kept but not used, and they are used again when the owner is back on Pro. ## 3. Which calls use the brand Only the hosted service applies a brand, because the colors are saved with the organisation in your Plotwork account. | Call | Brand it uses | |---|---| | A tool of the hosted MCP server, or a REST drawing route, with an organisation API key | That organisation's brand | | The same with your own API key, or an agent you connected by signing in | The brand you chose under "Brand for my diagrams" in the portal's Settings, while you are still a member of that organisation | | Publishing to an organisation's library | That organisation's brand, whoever publishes | | Publishing to your own library | The brand you chose in Settings | | The local MCP server, the npm package, the command line and the GitHub Action | None: the built-in theme | A published picture is drawn once, when you publish it. Changing the brand later does not change a picture that is already published; publish it again to get a new picture in the new colors. You choose the brand for your own calls in the portal's Settings, from the organisations you belong to that have one. If you choose none, your own calls draw the built-in theme. ## 4. Privacy A brand is organisation configuration, like its name. Plotwork stores the colors, the id of the account that last saved them, and when. Your choice in Settings is stored as your account id, the organisation's id and a time. No diagram is stored to make or apply a brand. See the [privacy policy](https://docs.plotwork.dev/privacy-policy.md). ## See also | Doc | Why | |---|---| | [Custom icons](https://docs.plotwork.dev/custom-icons.md) | Draw your company's own logos in the same diagrams | | [Organisations](https://docs.plotwork.dev/organisations.md) | Roles, members and the organisation's private library | | [Plans and billing](https://docs.plotwork.dev/plans-and-billing.md) | What Free and Pro include, and how to upgrade | | [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md) | Publishing to your own library | | [MCP server](https://docs.plotwork.dev/mcp/overview.md) | How the local and the hosted server differ | | [Privacy policy](https://docs.plotwork.dev/privacy-policy.md) | What is stored about an organisation and its brand | --- # Custom icons > Page: https://docs.plotwork.dev/custom-icons. Index of every page: https://docs.plotwork.dev/llms.txt Upload your company's own service logos to an organisation, and the hosted service draws them in your diagrams as `icon=org:<name>`. Icon search, `map_icons` and automatic icon matching find them by name and by alias. ## TL;DR | Question | Answer | |---|---| | What is a custom icon? | An SVG or PNG logo of at most 32 KB, saved on an organisation under a name such as `payments-api`. A diagram uses it as `icon=org:payments-api`. | | Which plan? | Pro. The organisation's owner must be on Pro for icons to be uploaded and drawn. Pro keeps up to 100 icons per organisation. | | Who can upload? | The organisation's owner or an admin, on the organisation's Icons tab in the portal. Members and readers see the icons. | | Which calls draw them? | The hosted MCP server, the REST API and publishing, for the organisation's API keys and for members who chose the organisation for their own calls. | | Does the local package draw them? | No. The local MCP server, the npm package, the command line and the GitHub Action know no `org:` name, and report one as an unknown icon. | | Is an uploaded SVG safe? | It is read with a strict parser and written again from an allowlist of drawing elements. Scripts, event handlers, embedded HTML, style blocks and any link outside the file are refused with a reason. The picture is then drawn as an image, which cannot run code or load anything. | | Can an agent upload an icon? | No. No tool takes an icon. The hosted service picks the icons from who is calling. | ## 1. Upload an icon 1. Open the organisation in the portal and choose the Icons tab. 2. Drop an SVG or PNG file on the upload area, or choose one. 3. Check the name. It is made from the file name, and you can change it: lowercase letters and digits joined by hyphens, at most 48 characters. 4. Add aliases, separated by commas, such as `payments service, Acme Pay`. Search and automatic matching find the icon by each of them. 5. Choose Upload icon. Each icon is shown on a light and a dark background, the way diagrams draw it. Upload a file with a name that is already taken to replace that icon. To delete one, choose Remove on its tile. | Role | Can do | |---|---| | Owner | See, upload, replace and delete icons | | Admin | See, upload, replace and delete icons | | Member | See the icons | | Reader | See the icons | | Organisation API key | See the icons, and draw with them | If the owner moves from Pro to Free, the icons are kept but not drawn, and they are drawn again when the owner is back on Pro. ## 2. What a file may hold The upload check keeps drawing and nothing else. | Kept | Refused, with a reason | Left out quietly | |---|---|---| | Paths, circles, ellipses, rectangles, lines, polylines, polygons, groups | `script`, event handlers such as `onload` | Editor metadata, `desc`, Inkscape and Sodipodi data | | Linear and radial gradients, clip paths, masks, inside the file | `foreignObject`, `iframe`, `image`, `use`, `a`, animations | `class` and `data-` attributes | | Fill, stroke, opacity, transform, and the same properties in a `style` attribute | A `<style>` block: export with presentation attributes | Style properties that do not paint | | A title | A DOCTYPE, entities, CDATA, a link or `url()` to anything outside the file | | | | Text that is not converted to outlines, filters, patterns | | | Limit | Value | |---|---| | File size | 32 KB | | Elements | 2,000 | | Nesting | 24 levels | | Path commands | 4,000 | | PNG size | 1,024 pixels on each side | | Aliases | 10, each at most 60 characters | A PNG keeps only the chunks that draw the picture, so text and EXIF metadata, which can hold a location, are not stored. Every id in an SVG is renamed after the icon, so two icons never share one. When a file is refused, the Icons tab lists every reason beside the file, each with how to fix it. The REST API answers `422 icon.invalid` with the same list in `details`. ## 3. Use an icon in a diagram Write the name after `org:`, the same way as any other icon: ```plotwork flow "Checkout" direction=right node shop service "Storefront" node pay service "Acme Pay" icon=org:acme-pay node books database "Ledger" icon=org:ledger shop -> pay pay -> books ``` An organisation icon is drawn in its own colors on a light tile, in both themes, like an official cloud icon. A node with no `icon=` whose whole label is an icon's name or alias, such as `"Acme Pay"`, draws that icon by itself, as it does for a bundled product name. With `icons=off` in the header it is not drawn, and the check suggests `icon=org:acme-pay` instead. | Tool | What it does with your icons | |---|---| | `search_icons` | Lists `org:` icons first when they match as well as a bundled icon | | `map_icons` | Resolves a name that is an icon's name or alias to that icon, with confidence `exact` | | `draw_diagram`, `render_diagram`, `publish_diagram` | Draws `org:` icons | | `validate_diagram` | Accepts `org:` names that exist, and suggests the nearest one for a misspelling | ## 4. Which calls use which icons | Caller | Icons | |---|---| | An organisation's API key | That organisation's icons | | A personal key, an agent connected by sign-in, the portal | The icons of the organisation chosen under "Organisation style for my own diagrams" on the Organisations page, or named by `brand.organization` in the repository settings, while you are a member | | A diagram published into an organisation's library | That organisation's icons, whoever publishes | | The local MCP server, the npm package, the CLI, the GitHub Action | None | A picture that is already published keeps the icon it was drawn with, even after the icon is replaced or deleted. Publish it again to draw the new one. ## 5. Privacy An icon is organisation configuration, like a brand: the file the check wrote, its name and aliases, the account that uploaded it, and when. It is deleted with the organisation. No diagram is stored to draw one. The operator's audit trail records that an icon was saved or deleted, with the organisation's id, and never its name or file. On Pro and Enterprise the organisation's own [audit log](https://docs.plotwork.dev/audit-log.md) records the icon's name and who saved or deleted it, never the file. ## See also | Page | Why | |---|---| | [Brand themes](https://docs.plotwork.dev/brand-themes.md) | Paint the same diagrams in your company's colors | | [Organisations](https://docs.plotwork.dev/organisations.md) | Roles, keys and the library icons sit beside | | [Cloud icon names](https://docs.plotwork.dev/cloud-architecture/icon-names.md) | The bundled icons and how names are written | | [Plans and billing](https://docs.plotwork.dev/plans-and-billing.md) | What Pro includes | | [Privacy policy](https://docs.plotwork.dev/privacy-policy.md) | What is stored, and for how long | --- # Audit log > Page: https://docs.plotwork.dev/audit-log. Index of every page: https://docs.plotwork.dev/llms.txt An organisation's audit log says who changed what in the organisation, and when. The owner and the admins read it in the portal, and on Enterprise they export it, or have their SIEM poll it with a key that can do nothing else. ## TL;DR | Question | Answer | |---|---| | Which plan? | Pro keeps 30 days of events and shows them in the portal. Enterprise keeps 365 days, or the days the contract sets, and adds the export. The Free plan records nothing. | | Who can read it? | The owner and the admins, on the organisation's Audit log tab. A member and a reader cannot. On Enterprise, an organisation API key with the `audit:read` scope can too. | | What is recorded? | Members, invitations and roles, ownership, API keys, diagrams, folder rules, the brand, icons, avatar and style rules, "Keep diagrams editable", drift reports from CI, single sign-on (its settings, its domains, every sign-in through your provider, failed ones, people added on first sign-in, and the owner's emailed-code sign-ins while it is required), and changes Plotwork support made, with the reason given. | | What is never in it? | A diagram's source, a key's value, a token, a color, or a full IP address. | | How does a SIEM get it? | It polls `GET /orgs/{org}/audit/export` with a key that has only `audit:read`, and sends the `Plotwork-Next-Cursor` header back as `after` each time. | | Is there a push? | Yes, on Enterprise: each event can be sent as it happens to Datadog, Dynatrace, Splunk, Microsoft Sentinel or a signed webhook. See [Send audit events and drift to your tools](https://docs.plotwork.dev/siem-delivery.md). Polling the export still works, and is the record to reconcile against. | ## 1. Read the log Open the organisation in the portal and choose the Audit log tab. The newest events come first, a page at a time, with a button for older ones. | Column | What it shows | |---|---| | When | How long ago, with the exact time on hover | | Who | A member's email address, a key's name tagged API key, CI tagged CI, or Plotwork support | | What | What happened, with facts such as the role given or the setting chosen | | On | What it was done to: a member, an invited address, a key, a diagram, an icon or a folder | | From | The network the request came from: an IPv4 /24 or an IPv6 /48, never the full address | Three filters narrow the list, and each reaches every event the plan keeps: | Filter | Narrows to | |---|---| | What | One group of actions, such as Members or API keys | | Who | One email address or key name, as the log shows it. Choose a suggestion or press Enter. | | When | The last 24 hours, 7 days, or 30 days | ## 2. Export it (Enterprise) The tab has two downloads, CSV and JSON Lines, with the filters you chose. One file holds up to 1,000 events, oldest first. Downloading is itself recorded. The same export is an API route: ```bash curl -s "https://api.plotwork.dev/orgs/acme-platform/audit/export?format=jsonl" \ -H "X-API-Key: $PLOTWORK_AUDIT_KEY" -D headers.txt -o events.jsonl ``` | Parameter or header | Meaning | |---|---| | `format` | `jsonl` (the default) or `csv` | | `after` | Where to start: the `Plotwork-Next-Cursor` header of the export before | | `actor`, `action`, `target`, `from`, `to` | The same filters as the log. `action` takes an action or a group such as `orgs.member`; `from` and `to` take ISO 8601 times. | | `Plotwork-Next-Cursor` | The last event in this export. Send it as `after` next time. | | `Plotwork-More` | `true` when more events are waiting after this export: ask again at once. | A cell in the CSV that a spreadsheet would run as a formula starts with `'`, because a diagram's name is text a member typed. ## 3. Connect a SIEM 1. In the organisation's Keys tab, create a key and choose "Audit log only: read and export it". That key reads the log and is refused everywhere else, so it cannot publish or delete. 2. Store the key in the SIEM's secret store. 3. Poll the export every few minutes. Keep the last `Plotwork-Next-Cursor` you saw, and send it as `after`. While `Plotwork-More` is `true`, ask again straight away. 4. Parse each line against the [event schema](https://docs.plotwork.dev/schemas/plotwork.audit-event.json). | SIEM | How it polls | |---|---| | Splunk | An HTTP input or a scripted input that runs the request above and keeps the cursor in a checkpoint | | Datadog | A small job that runs the request and sends each line to the Logs intake | | Microsoft Sentinel | A codeless connector or a logic app with a REST API poller, paging by the `after` parameter | A key's export is not recorded as an event, so polling does not fill the log with its own reads. The key's last use is on the Keys tab. ## 4. The event Every event has the same shape, version 1. A field may be added; none is removed or changes meaning without a new version. | Field | Meaning | |---|---| | `version` | `1` | | `id` | The event's id | | `orgId` | The organisation's id | | `at` | When it happened, as an ISO 8601 time in UTC | | `action` | What happened, such as `orgs.member.role` (the list is below) | | `actor.kind` | `user`, `key`, `ci` or `operator` | | `actor.id` | The account id, the key id, or null for Plotwork support | | `actor.name` | The email address or key name when it was recorded, "Plotwork support", or null once a deleted account is forgotten | | `target.type`, `target.id`, `target.name` | What it was done to: its kind, its id, and its name or address | | `metadata` | A few facts, such as `role`, `keepEditable`, `folder`, `scopes` or `reason` | | `ip` | The network the request came from, or null | | `userAgent` | The first 200 characters of the user agent, or null | | `requestId` | The request id, which Plotwork support can look up | | Group | Actions | |---|---| | Organisation | `orgs.org.create`, `orgs.org.rename` | | Members | `orgs.member.add`, `orgs.member.remove`, `orgs.member.leave`, `orgs.member.role`, `orgs.owner.transfer` | | Invitations | `orgs.invite.create`, `orgs.invite.revoke`, `orgs.invite.accept` | | API keys | `orgs.key.create`, `orgs.key.reveal`, `orgs.key.revoke` | | Diagrams | `orgs.diagram.publish`, `orgs.diagram.update`, `orgs.diagram.edit`, `orgs.diagram.restore`, `orgs.diagram.lock`, `orgs.diagram.unlock`, `orgs.diagram.delete` | | Folder rules | `orgs.folder_rule.set`, `orgs.folder_rule.clear` | | Look | `orgs.brand.set`, `orgs.brand.clear`, `orgs.icon.set`, `orgs.icon.delete`, `orgs.avatar.set`, `orgs.avatar.clear`, `orgs.style_rules.set`, `orgs.style_rules.clear` | | Settings | `orgs.settings.editable`, `orgs.settings.publishing` | | Members' public diagrams | `orgs.publishing.move`, `orgs.publishing.unpublish` | | CI | `orgs.drift.report` | | Contract | `orgs.contract.set`, `orgs.contract.end` | | Integrations | `orgs.integration.create`, `orgs.integration.update`, `orgs.integration.delete` | | The log itself | `orgs.audit.export` | ## 5. How long events are kept | Plan | Kept | |---|---| | Free | Nothing is recorded | | Pro | 30 days | | Enterprise | 365 days, or the days the contract sets | A daily clean-up removes older events. When an organisation moves to a plan that keeps fewer days, the next clean-up removes what that plan does not keep. The log goes with the organisation. When an account is deleted, its email address is cleared from every log and its id stays. ## See also | Page | Why | |---|---| | [Organisations](https://docs.plotwork.dev/organisations.md) | Roles, keys and who may do what | | [Single sign-on](https://docs.plotwork.dev/single-sign-on.md) | The sign-in events this log records | | [Plans and billing](https://docs.plotwork.dev/plans-and-billing.md) | What Pro and Enterprise include | | [Enterprise rollout](https://docs.plotwork.dev/enterprise-rollout.md) | Keys and CI across many repositories | | [Send audit events and drift to your tools](https://docs.plotwork.dev/siem-delivery.md) | Push each event to Datadog, Dynatrace, Splunk, Microsoft Sentinel or a webhook | | [Privacy policy](https://docs.plotwork.dev/privacy-policy.md) | What the log stores, and for how long | | [API reference](https://docs.plotwork.dev/api/get-orgs-org-audit.md) | The read and export routes | --- # Send audit events and drift to your tools > Page: https://docs.plotwork.dev/siem-delivery. Index of every page: https://docs.plotwork.dev/llms.txt On Enterprise, an organisation can send every event of its audit log, and every drift run its CI reports, to the tools it already runs: Datadog, Dynatrace, Splunk, Microsoft Sentinel, or an HTTPS webhook of its own. Plotwork sends each one as it happens, in batches, and tries again for a day when a tool is down. ## TL;DR | Question | Answer | |---|---| | Which plan? | Enterprise. On another plan the Integrations tab says so, nothing is sent, and a destination saved earlier can still be removed. | | Who sets it up? | The owner and the admins, signed in, on the organisation's Integrations tab in the portal. | | Where can it go? | Datadog (logs, and drift as metrics), Dynatrace (logs, and drift as metrics), Splunk (HTTP Event Collector), Microsoft Sentinel (the Azure Monitor Logs Ingestion API), or a webhook signed with HMAC-SHA256. Up to five destinations. | | What is sent? | Metadata only: the same fields as the audit log's export and the drift dashboard. Never a diagram's source, a key's value or a token. | | How fast? | About five seconds after an event, in batches of up to 100. | | What if my tool is down? | Plotwork tries again after 30 seconds, then waits twice as long each time, up to an hour, for a day. After a day the events are dropped and counted. | | How are secrets kept? | Sealed with AES-256-GCM as soon as they are saved, and never shown again, not even to the owner. Leave the field empty when you edit to keep the saved one. | | Can Plotwork reach my private network? | No. Plotwork sends only to HTTPS addresses on public names. | ## 1. Add a destination 1. Open the organisation in the portal and choose the **Integrations** tab. 2. Choose the tool, give the destination a name, and choose what it receives: the audit log, the drift results, or both. 3. Fill in the tool's fields from the section below, then save. 4. Press **Send test event**. Plotwork sends one event of type `test` at once and says whether the tool took it, or why not, in one sentence such as "Datadog answered HTTP 403." Each destination then shows, in plain sentences, when it last took a batch, its last error, how many events wait to be sent, how many were given up on, and when it tries next. ## 2. Datadog Audit events and drift runs go to the Logs intake, `https://http-intake.logs.<site>/api/v2/logs`. Drift runs also go to the metrics API, `https://api.<site>/api/v2/series`. | Field | What to enter | |---|---| | Site | The site your organisation signs in to, from the table below | | API key | An API key from Organization Settings, API Keys. Not an application key. It is sent as the `DD-API-KEY` header. | | Site | Domain | |---|---| | US1 | `datadoghq.com` | | US3 | `us3.datadoghq.com` | | US5 | `us5.datadoghq.com` | | EU1 | `datadoghq.eu` | | AP1 | `ap1.datadoghq.com` | | US1-FED (`gov`) | `ddog-gov.com` | Each log has `ddsource` and `service` set to `plotwork`, a `plotwork_type` tag (`audit_event`, `drift_run` or `test`), a one-line summary as its message, and the whole event under the `plotwork` attribute. For each diagram a drift run checked, Plotwork sends two gauges tagged `repo:<repository>` and `source:<name>`: | Metric | Value | |---|---| | `plotwork.drift.stale` | 1 when the diagram drifted from its files or could not be made, 0 when it is in step or was written | | `plotwork.drift.checked` | 1 | A point carries the time it is sent, because Datadog refuses points more than an hour old and a retried batch can be older. ## 3. Dynatrace Audit events and drift runs go to Log Monitoring API v2, `https://<environment id>.live.dynatrace.com/api/v2/logs/ingest`. Drift runs also go to Metrics API v2, `/api/v2/metrics/ingest`, as line protocol. | Field | What to enter | |---|---| | Environment id | The first part of your environment's address, such as `abc12345` in `abc12345.live.dynatrace.com` | | Access token | A token with the **Ingest logs** (`logs.ingest`) and **Ingest metrics** (`metrics.ingest`) scopes. It is sent as `Authorization: Api-Token <token>`. | Each log record has the event as JSON in `content`, its time in `timestamp`, `log.source` set to `plotwork`, and the attributes `plotwork.type`, `plotwork.org_id`, and `plotwork.action` or `plotwork.repository`. The drift metrics are `plotwork.drift.stale` and `plotwork.drift.checked`, gauges with the dimensions `repo` and `source`, as for Datadog, stamped by Dynatrace when they arrive. ## 4. Splunk Events go to the HTTP Event Collector. | Field | What to enter | |---|---| | Address | The collector's event endpoint, such as `https://http-inputs-<stack>.splunkcloud.com/services/collector/event` on Splunk Cloud, or `https://splunk.example.com:8088/services/collector/event` on a server you run. HTTPS on port 443 or 8088. | | Index | The index to write to, or empty for the token's default | | Token | An HTTP Event Collector token. It is sent as `Authorization: Splunk <token>`. Turn indexer acknowledgement off for this token. | Each event has `host` set to `plotwork.dev`, `source` set to `plotwork`, a `sourcetype` of `plotwork:audit_event`, `plotwork:drift_run` or `plotwork:test`, its time in seconds, and the whole event as `event`. ## 5. Microsoft Sentinel Events go to a Log Analytics workspace through the Azure Monitor Logs Ingestion API, with a token Plotwork asks Microsoft Entra ID for with the client credentials you give. Plotwork keeps the token in memory until five minutes before it expires, and never stores it. Set it up in Azure once: 1. In Microsoft Entra ID, register an application and add a client secret. Note the tenant id, the application (client) id and the secret's value. 2. In the workspace that Sentinel uses, create a custom table from a data collection rule (Tables, Create, New custom log (DCR-based)), named for example `PlotworkAudit_CL`, with the columns below. Azure creates the rule and its stream, `Custom-PlotworkAudit_CL`. 3. Give the application the **Monitoring Metrics Publisher** role on the data collection rule. 4. From the rule's JSON view, copy its `immutableId`. From its data collection endpoint (or the rule's own logs ingestion endpoint), copy the logs ingestion address. | Column | Type | Holds | |---|---|---| | `TimeGenerated` | datetime | When the event happened | | `RecordType` | string | `audit_event`, `drift_run` or `test` | | `OrgId` | string | The organisation's id | | `Summary` | string | A one-line summary, such as `orgs.member.role by ada@acme.com on dev@acme.com` | | `Data` | dynamic | The whole event | | Field | What to enter | |---|---| | Tenant | The tenant id or its primary domain | | Client id | The application (client) id | | Client secret | The secret's value | | Endpoint | The logs ingestion address, on `*.ingest.monitor.azure.com` | | Rule id | The rule's immutable id, `dcr-` and 32 hexadecimal characters | | Stream | The stream, such as `Custom-PlotworkAudit_CL` | ## 6. A webhook Plotwork posts each batch to your HTTPS address as a JSON array of events. | Header | Value | |---|---| | `Content-Type` | `application/json` | | `Plotwork-Signature` | `t=<unix seconds>,v1=<hex>`: HMAC-SHA256 under your signing secret of the timestamp, a full stop, and the raw body | | `Plotwork-Delivery` | The id of the batch's first event, so a retry of the same batch carries the same value | | `User-Agent` | `Plotwork-Delivery/1` and a link to this page | | Field | What to enter | |---|---| | Address | Your endpoint. HTTPS on port 443, on a public name. | | Signing secret | At least 32 characters. **Generate** in the portal fills in a random one; copy it to your receiver before you save, because it is not shown again. | Answer with a 2xx status within 10 seconds. Any other status, a redirect, or no answer is a failure, and the batch is sent again later. Check the signature over the body exactly as it arrived, before parsing it, and refuse a timestamp more than five minutes from your clock, so a captured request cannot be replayed. An event can arrive twice, so use its `id` to skip one you have seen. In Node: ```js import { createHmac, timingSafeEqual } from 'node:crypto' export function verifyPlotworkSignature(secret, header, rawBody, toleranceSeconds = 300) { const parts = Object.fromEntries(header.split(',').map((part) => part.trim().split('=', 2))) const timestamp = Number(parts.t) if (!Number.isInteger(timestamp) || Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex') const given = String(parts.v1 ?? '') return given.length === expected.length && timingSafeEqual(Buffer.from(given), Buffer.from(expected)) } ``` In Python: ```python import hashlib import hmac import time def verify_plotwork_signature(secret: str, header: str, raw_body: bytes, tolerance_seconds: int = 300) -> bool: parts = dict(part.strip().split("=", 1) for part in header.split(",") if "=" in part) try: timestamp = int(parts.get("t", "")) except ValueError: return False if abs(time.time() - timestamp) > tolerance_seconds: return False expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, parts.get("v1", "")) ``` ## 7. What is sent Every event has a `type`, a `version` (1), an `id`, the organisation's `orgId`, and `at`, an ISO 8601 time in UTC. | Type | Fields | |---|---| | `audit_event` | The fields of [`plotwork.audit-event.json`](https://docs.plotwork.dev/schemas/plotwork.audit-event.json), the shape the audit log's export uses: `action`, `actor` (`kind`, `id`, `name`), `target` (`type`, `id`, `name`), `metadata`, `ip` (the network only), `userAgent` and `requestId`. The [audit log guide](https://docs.plotwork.dev/audit-log.md) lists every action. | | `drift_run` | What the GitHub Action reported: `repository`, `ref`, `sha`, `event`, `runUrl`, `pullRequestUrl`; `counts` with `sources`, `inStep`, `drifted`, `written`, `failed` and `errors`; and `sources`, each with `name`, `out`, `status`, `added`, `removed`, `changed`, `errors` and `owners`. The [drift dashboard guide](https://docs.plotwork.dev/drift-dashboard.md) explains each. | | `test` | `message`, from **Send test event** | Nothing else is sent: never a diagram's source, a file's content, the names of what changed, a key's value or a token. ## 8. How delivery works | Question | Answer | |---|---| | When is an event sent? | About five seconds after the first event of a burst, so a burst goes as one batch. | | How big is a batch? | Up to 100 events and 512 KB. Up to 10 batches go out at a time, then the rest a second later. | | In what order? | Oldest first, for each destination. | | What counts as a failure? | Any answer that is not 2xx, a redirect, no answer within 10 seconds, or an address that is not public HTTPS. The events stay in the queue. | | How often is a failure tried again? | After 30 seconds, then 1, 2, 4 and 8 minutes and so on, up to once an hour, each a fifth earlier or later at random. | | When does Plotwork give up? | An event still unsent a day after it happened is dropped and counted as given up on. So is the oldest event when 10,000 wait for one destination. | | Can an event arrive twice? | Yes. When a tool takes a batch but its answer is lost, the batch is sent again. Use each event's `id`. | | What does the status say when something fails? | One sentence of Plotwork's own, such as "Splunk answered HTTP 401." or "The webhook did not answer within 10 seconds." Never what your tool answered. | | What happens when I remove a destination? | Its sealed secret is deleted with it, and so is everything waiting for it. | | What does "Paused by Plotwork support" mean? | Plotwork support stopped sending to the destination, usually because it kept failing. Events keep waiting for it and are given up on after a day, as when it fails. `status.pausedAt` says when. Ask support why, or to resume it; nothing you set up is changed. | | What can Plotwork support see? | Each destination's kind, name, host and delivery status, to answer a question about it. Never its secret, its full address or the events. | ## 9. The API The Integrations tab uses these routes. They take the portal's session, the owner's or an admin's, and are not in the OpenAPI reference. | Route | Does | |---|---| | `GET /orgs/{org}/integrations` | Lists the destinations: the fields that are not secret, `secretSet` in place of the secret, and each one's `status`, with `available` saying whether the plan sends | | `POST /orgs/{org}/integrations` | Adds one. The body has `type`, `name`, `streams` (`audit`, `drift`, or both), the type's fields and its secret field (`apiKey`, `token`, `clientSecret` or `signingSecret`). | | `PUT /orgs/{org}/integrations/{id}` | Changes one. The type cannot change; leave the secret out to keep the saved one. | | `DELETE /orgs/{org}/integrations/{id}` | Removes one and what waits for it | | `POST /orgs/{org}/integrations/{id}/test` | Sends a test event now and answers `{ "ok": true }` or `{ "ok": false, "error": "<one sentence>" }` | | Error code | Status | Means | |---|---|---| | `integrations.invalid` | 400 | A field is wrong. `details.reason` is `address` (not a public HTTPS address on an allowed port), `secret_required`, `field` or `type_change`, and `details.field` names the field. | | `plan.siem_delivery_not_included` | 403 | The organisation's plan does not send to its tools | | `orgs.forbidden` | 403 | Only the owner and the admins can set it up | | `integrations.limit` | 409 | The organisation has five destinations already | | `integrations.unavailable` | 503 | Delivery is not set up on this server | Adding, changing and removing a destination is recorded in the audit log as `orgs.integration.create`, `orgs.integration.update` and `orgs.integration.delete`, without the secret. ## See also | Page | Why | |---|---| | [Audit log](https://docs.plotwork.dev/audit-log.md) | Every action, the export, and polling it with a key | | [The drift dashboard](https://docs.plotwork.dev/drift-dashboard.md) | What a drift run reports | | [Single sign-on](https://docs.plotwork.dev/single-sign-on.md) | The sign-in events the log records | | [Plans and billing](https://docs.plotwork.dev/plans-and-billing.md) | What Enterprise includes | | [Privacy policy](https://docs.plotwork.dev/privacy-policy.md) | What is stored to send, and for how long | --- # Single sign-on > Page: https://docs.plotwork.dev/single-sign-on. Index of every page: https://docs.plotwork.dev/llms.txt An Enterprise organisation can sign its members in through its own identity provider with OpenID Connect or SAML 2.0. Your people use the account they already have at Okta, Microsoft Entra ID, Google Workspace or any other OpenID Connect or SAML provider, and you decide who joins and whether the emailed code still opens the organisation. ## TL;DR | Question | Answer | |---|---| | Which plan? | Enterprise. On another plan the Single sign-on tab says so and offers nothing to fill in. | | Which protocol? | OpenID Connect, with the authorization code flow and PKCE, or SAML 2.0 with signed assertions, started from Plotwork. One protocol at a time. SCIM 2.0 provisioning adds, updates and removes members and gives roles by group. | | Who sets it up? | The owner or an admin, in the portal: open the organisation, then the Single sign-on tab. An API key cannot change it. | | What do I need? | An email domain you control, a DNS record you can publish for it, and an app registered at your provider: an OIDC web app, or a SAML app. | | Who can sign in this way? | Only people whose address is on a domain you verified. Nobody else, whatever the provider says. | | What do new people get? | Nothing, unless you turn on "Add people when they first sign in". Then they join as a member or a reader, never an admin, within your member limit. | | Can I make it the only way in? | Yes, with Require single sign-on. The owner keeps the emailed code as a way back in if the provider is down, and every such sign-in is recorded. | | Can my provider manage members? | Yes, with SCIM 2.0: it adds people, deactivates or removes them (their access ends at once), and moves them between groups that you map to roles. See section 7. | | Is the client secret safe? | It is sealed (AES-256-GCM) before it is stored, and it is never shown again, not even to you. | ## 1. Prove your email domain 1. In the Single sign-on tab, under Email domains, type the domain your members use, such as `acme.com`, and press Add domain. 2. Plotwork shows a TXT record to publish: | Field | Value | |---|---| | Type | `TXT` | | Name | `_plotwork-verify.acme.com` | | Value | `plotwork-verify=` followed by 64 characters, unique to your organisation and domain | 3. Publish it at your DNS host, then press Verify. Plotwork looks it up through Cloudflare's DNS over HTTPS. A record that is not found yet is not an error: DNS can take a few minutes to change, so press Verify again later. A domain can be verified by one organisation only. Public mail domains such as `gmail.com` and `outlook.com` cannot be added. You can add up to 20 domains, and leaving the record in place after it is verified is fine but not needed. ## 2. Register Plotwork at your provider Under Identity provider, choose the protocol your provider app speaks: OpenID Connect or SAML 2.0. Saving the other protocol later replaces the provider and turns single sign-on off until you turn it on again, so nobody is held to a provider that has not been tried. ### With OpenID Connect Register a web application at your provider. The tab shows the redirect URI to give it: | Environment | Redirect URI | |---|---| | Plotwork | `https://api.plotwork.dev/auth/sso/callback` | Then copy three values back into the tab under Identity provider: | Field in Plotwork | What it is | |---|---| | Issuer URL | The address that names your own tenant at the provider. Plotwork reads `<issuer>/.well-known/openid-configuration` to find the rest. | | Client ID | The id the provider gave the app | | Client secret | The secret the provider gave the app. Required the first time; leave it empty later to keep the one stored. | Press Save provider. Plotwork checks the issuer before it saves anything: the address must be HTTPS, the provider must offer the authorization code flow with PKCE and sign ID tokens with RS256 or ES256, and the issuer must be your own tenant. An issuer shared by every tenant, such as Microsoft's `/common`, is refused. If a check fails, the tab says which one. ### With SAML 2.0 Create a SAML app at your provider. The tab shows three values for it, each with a copy button. They are your organisation's own, so an assertion made for another organisation never signs anyone in to yours: | Value in Plotwork | Where it goes at your provider | Example | |---|---|---| | Entity ID (Audience URI) | The service provider entity ID, audience or identifier | `https://api.plotwork.dev/auth/sso/saml/<organisation id>` | | ACS URL (Single sign-on URL) | The assertion consumer service, reply URL or single sign-on URL. Also the recipient and destination, where the provider asks. | `https://api.plotwork.dev/auth/sso/saml/<organisation id>/acs` | | Plotwork metadata URL | For a provider that can read a service provider's metadata | `https://api.plotwork.dev/auth/sso/saml/<organisation id>/metadata` | Set the provider to send the person's email address as the NameID, to sign the assertion (or the whole response) with SHA-256, and not to encrypt the assertion. Then give Plotwork the provider's metadata, either its address under Metadata URL or the XML pasted under Or paste the metadata XML, and press Save provider. Plotwork reads the metadata before it saves anything: it needs the provider's entity ID, a sign-in URL on the redirect binding, and an RSA signing certificate of at least 2048 bits. If a check fails, the tab says which one. When the NameID is not the address, such as an opaque persistent ID, put the name of the attribute that carries the address under Email attribute. Leave it empty to read the NameID. When your provider rotates its signing certificate, save the provider again with the same metadata URL (or the new XML). The tab lists each certificate with its fingerprint and expiry date. Plotwork accepts only sign-ins it started: a response must answer the request Plotwork sent, in the same browser, within ten minutes. Starting from the app tile at your provider (IdP-initiated sign-in) is refused; people start from Plotwork's sign-in page. Step-by-step pages for the three providers most teams use, for both protocols: | Provider | Guide | |---|---| | Okta | [Single sign-on with Okta](https://docs.plotwork.dev/single-sign-on-okta.md) | | Microsoft Entra ID | [Single sign-on with Microsoft Entra ID](https://docs.plotwork.dev/single-sign-on-entra-id.md) | | Google Workspace | [Single sign-on with Google Workspace](https://docs.plotwork.dev/single-sign-on-google-workspace.md) | ## 3. Turn it on The Sign-in rules section has three switches and a role: | Setting | What it does | |---|---| | Sign in with single sign-on | People on your verified domains are offered your provider when they type their address on the sign-in page. | | Require single sign-on | Members and admins on a verified domain open the organisation only after signing in through your provider. It needs the connection on and a verified domain. | | Add people when they first sign in | Someone on a verified domain who is not a member yet joins when they first sign in, if there is room in your member limit. | | Role for new people | Member or reader. Never admin: promote a person on the Members tab. | Turning the connection off, removing the provider, or removing the last verified domain turns Require single sign-on off at the same moment, so nobody is locked out by a half-finished set-up. ## 4. How people sign in 1. On the sign-in page, a person types their work address. 2. If the address is on a verified domain of an organisation with single sign-on on, the page offers Continue with single sign-on, with Email me a code instead beside it. 3. Their browser goes to your provider, they sign in there, and they come back signed in to Plotwork. The first time, Plotwork links the provider's account to the Plotwork account with the same address, or makes one. After that the link wins: if the person's address changes at the provider, they still sign in to the same Plotwork account. If a sign-in fails, the sign-in page says why in words: | Shown when | Meaning | |---|---| | The link has expired or was opened in another browser | The sign-in took longer than ten minutes, or the provider sent the browser back to a different browser. Start again. | | Your identity provider did not sign you in | The person cancelled, or the provider refused them. | | Your identity provider sent a sign-in Plotwork could not trust | The ID token or the SAML response failed a check: its signature, issuer, audience, recipient, time or nonce. Check the issuer and the client ID, or for SAML the entity ID, the ACS URL, that the assertion is signed and not encrypted, and that the metadata is current. | | Your address is not on a domain your organisation verified | The provider named an address outside your verified domains, or said the address is not verified. | | You are not a member of this organisation yet | Adding people on first sign-in is off. Invite the person. | | Your organisation has no free seats | Adding people on first sign-in is on, and the member limit is reached. | ## 5. Require single sign-on and the way back in With Require single sign-on on, a member or an admin whose address is on a verified domain and who signed in with an emailed code sees "Single sign-on required" on the organisation's pages, with a button that signs them in through your provider and brings them back. The owner is never held to it. If your provider is down or set up wrongly, the owner signs in with an emailed code, opens the Single sign-on tab and fixes it. Every such sign-in is recorded in the audit log as a break-glass sign-in, so your security team sees it. API keys and connected agents are not held to Require single sign-on: they reach the organisation through membership, which is checked on every call. Removing a member stops every key of theirs at once. ## 6. What is recorded Every change and every sign-in goes to the organisation's [audit log](https://docs.plotwork.dev/audit-log.md): | Event | When | |---|---| | Changed single sign-on | The provider was saved, changed or removed, or a switch changed, with its protocol. The secret is never recorded. | | Added, checked or removed a single sign-on domain | A domain changed. A check records whether the record was found. | | Signed in with single sign-on | A person signed in through your provider. | | Failed to sign in with single sign-on | A sign-in failed after it started, with the reason. | | Joined through single sign-on | A person was added on first sign-in, with the role. | | Changed the SCIM token | A token was made, replaced or revoked. The token is never recorded. | | Provisioned, updated, deactivated or deprovisioned a person over SCIM | Your provider changed a member. | | Changed a SCIM group, or a role to follow SCIM groups | Your provider changed a group, you chose its role, or a person's role followed their groups. | | Signed in with an emailed code while single sign-on is required | The owner used the way back in. | ## 7. Provisioning with SCIM SCIM lets your identity provider manage your members for you. Set it up in the Provisioning with SCIM section of the tab: | Value | Where it goes at your provider | |---|---| | SCIM base URL, `https://api.plotwork.dev/scim/v2/<organisation id>` | The SCIM connector base URL, or tenant URL | | Token, from Make token | The bearer token, or secret token. It is shown once: copy it straight away. Replace token makes a new one and stops the old one at once. | Then turn provisioning on at your provider for creating, updating and deactivating users, and for pushing groups. Step-by-step pages for Okta and Microsoft Entra ID are below. | What your provider does | What happens in Plotwork | |---|---| | Creates a person | Their address must be on a domain you verified. The account is found or made, and they join as a member, within your member limit. | | Updates a person | Their name is kept as the provider sends it. Their address cannot change: deprovision them and create the new one. | | Deactivates a person | They leave the organisation at once, and every session they opened through your provider ends. Their API keys and agents stop at their next call. | | Deletes a person | The same, and the provider's record of them goes. Their Plotwork account stays, with their own diagrams and other organisations. | | Pushes a group | It appears under Groups and roles. | Under Groups and roles, choose the role each group gives: Admin, Member, Reader or No role. While no group gives a role, SCIM never changes anyone's role. Once one does, each provisioned person's role follows their groups: the highest role their groups give, or Member when none of theirs gives one. The owner is never changed or removed. Plotwork supports filtering users by `userName` and `externalId`, groups by `displayName`, paging with `startIndex` and `count`, and PATCH. It does not support bulk operations, sorting or password changes. ## See also | Page | Why | |---|---| | [Organisations](https://docs.plotwork.dev/organisations.md) | Roles, members and invitations | | [Audit log](https://docs.plotwork.dev/audit-log.md) | Where every sign-in and change is recorded | | [Plans and billing](https://docs.plotwork.dev/plans-and-billing.md) | What Enterprise includes | | [Privacy policy](https://docs.plotwork.dev/privacy-policy.md) | What single sign-on stores, and for how long | --- # Single sign-on with Okta > Page: https://docs.plotwork.dev/single-sign-on-okta. Index of every page: https://docs.plotwork.dev/llms.txt Connect an Enterprise organisation to Okta with OpenID Connect or SAML 2.0. Prove your email domain in Plotwork first, as the [single sign-on guide](https://docs.plotwork.dev/single-sign-on.md) describes. ## TL;DR | Plotwork field | Where it comes from in Okta | |---|---| | Issuer URL | Your Okta domain, such as `https://acme.okta.com`. With a custom authorization server, its issuer, such as `https://acme.okta.com/oauth2/default`. | | Client ID | The app's General tab, Client Credentials, Client ID | | Client secret | The app's General tab, Client Credentials, the secret under Client secrets | | Redirect URI to give Okta | `https://api.plotwork.dev/auth/sso/callback` | | SAML: Single sign-on URL and Audience URI to give Okta | The ACS URL and the Entity ID on Plotwork's Single sign-on tab | | SAML: Metadata URL | The app's Sign On tab, SAML Signing Certificates, the Metadata URL | ## 1. Create the app integration 1. In the Okta Admin Console, open Applications, then Applications, and press Create App Integration. 2. Choose OIDC - OpenID Connect as the sign-in method and Web Application as the application type, then press Next. 3. Fill in the form: | Okta field | Value | |---|---| | App integration name | `Plotwork` | | Grant type | Authorization Code only | | Sign-in redirect URIs | `https://api.plotwork.dev/auth/sso/callback` | | Sign-out redirect URIs | Leave empty | | Controlled access | The groups that should be able to sign in to Plotwork | 4. Press Save. ## 2. Copy the values into Plotwork 1. On the app's General tab, under Client Credentials, check that Client authentication is Client secret. You can also tick Require PKCE as additional verification: Plotwork always sends it. 2. Copy the Client ID, and the secret under Client secrets. 3. In Plotwork, open your organisation's Single sign-on tab and fill in Identity provider: | Plotwork field | Value | |---|---| | Issuer URL | `https://<your Okta domain>`, the address you sign in to the Admin Console with, without `-admin` | | Client ID | The Client ID you copied | | Client secret | The secret you copied | 4. Press Save provider, then turn on Sign in with single sign-on. ## 3. Check the address claim Plotwork reads the `email` claim of the ID token, and refuses it when `email_verified` is false. Okta sends both when the app asks for the `email` scope, which Plotwork does. The address must be on a domain you verified in Plotwork: a person whose Okta address is on another domain is refused, and the failure is recorded. ## Or connect with SAML 2.0 1. In the Okta Admin Console, open Applications, then Applications, press Create App Integration, choose SAML 2.0, and press Next. 2. Name the app `Plotwork` and press Next. 3. In Plotwork's Single sign-on tab, choose SAML 2.0 under Identity provider, then fill in Okta's Configure SAML step from it: | Okta field | Value | |---|---| | Single sign-on URL | The ACS URL from Plotwork. Leave Use this for Recipient URL and Destination URL ticked. | | Audience URI (SP Entity ID) | The Entity ID from Plotwork | | Name ID format | EmailAddress | | Application username | Email | | Response | Signed | | Assertion Signature | Signed | | Signature Algorithm | RSA_SHA256 | | Assertion Encryption | Unencrypted | 4. Press Next, then Finish. On the app's Sign On tab, under SAML Signing Certificates, copy the Metadata URL of the active certificate. 5. In Plotwork, paste it under Metadata URL, leave Email attribute empty, and press Save provider. Then turn on Sign in with single sign-on. 6. Assign the people or groups who should sign in on the app's Assignments tab. People start from Plotwork's sign-in page. Starting from the Plotwork tile on the Okta dashboard is refused, because Plotwork accepts only sign-ins it started; hide the tile under the app's General tab, App Settings, Do not display application icon to users. ## Provisioning with SCIM 1. In Plotwork's Single sign-on tab, under Provisioning with SCIM, press Make token and copy the token and the SCIM base URL. 2. In Okta, open the Plotwork app's General tab, press Edit under App Settings, set Provisioning to SCIM, and save. A Provisioning tab appears. 3. On the Provisioning tab, under Integration, press Edit and fill in: | Okta field | Value | |---|---| | SCIM connector base URL | The SCIM base URL from Plotwork | | Unique identifier field for users | `userName` | | Supported provisioning actions | Push New Users, Push Profile Updates, Push Groups | | Authentication Mode | HTTP Header | | Authorization (Bearer) | The token from Plotwork | 4. Press Test Connector Configuration, then Save. 5. Under Provisioning, To App, press Edit and turn on Create Users, Update User Attributes and Deactivate Users, then Save. 6. On the Push Groups tab, push the groups you want Plotwork to see. In Plotwork, choose the role each gives under Groups and roles. The SAML app and the OIDC app can both provision: use the one you sign in with. ## Troubleshooting | What you see | What to check | |---|---| | "The issuer could not be used: the document names a different issuer" | Use the issuer exactly as `https://<your Okta domain>/.well-known/openid-configuration` writes it, with or without `/oauth2/default`. | | "Your identity provider did not sign you in" | The person is not assigned to the app. Add them, or their group, under Assignments. | | "Your identity provider sent a sign-in Plotwork could not trust" | The Client ID in Plotwork is not this app's, or the issuer belongs to another authorization server. For SAML: the Single sign-on URL or the Audience URI is not Plotwork's, Assertion Encryption is on, or Okta rotated its certificate; save the provider again with the same Metadata URL. | | Okta says a user could not be created: the address is not on a verified domain | The person's Okta username is on a domain you have not verified in Plotwork. Verify it, or change the username. | | "The link has expired or was opened in another browser" after the Okta dashboard | The person started from the Okta tile. Start from Plotwork's sign-in page instead. | ## See also | Page | Why | |---|---| | [Single sign-on](https://docs.plotwork.dev/single-sign-on.md) | Domains, the switches, Require single sign-on and the way back in | | [Single sign-on with Microsoft Entra ID](https://docs.plotwork.dev/single-sign-on-entra-id.md) | The same for Microsoft Entra ID | | [Single sign-on with Google Workspace](https://docs.plotwork.dev/single-sign-on-google-workspace.md) | The same for Google Workspace | --- # Single sign-on with Microsoft Entra ID > Page: https://docs.plotwork.dev/single-sign-on-entra-id. Index of every page: https://docs.plotwork.dev/llms.txt Connect an Enterprise organisation to Microsoft Entra ID (formerly Azure Active Directory) with OpenID Connect or SAML 2.0. Prove your email domain in Plotwork first, as the [single sign-on guide](https://docs.plotwork.dev/single-sign-on.md) describes. ## TL;DR | Plotwork field | Where it comes from in Entra ID | |---|---| | Issuer URL | `https://login.microsoftonline.com/<Directory (tenant) ID>/v2.0` | | Client ID | The app registration's Overview, Application (client) ID | | Client secret | Certificates & secrets, Client secrets, the Value column (not the Secret ID) | | Redirect URI to give Entra ID | `https://api.plotwork.dev/auth/sso/callback`, as a Web platform redirect | | SAML: Identifier (Entity ID) and Reply URL to give Entra ID | The Entity ID and the ACS URL on Plotwork's Single sign-on tab | | SAML: Metadata URL | The enterprise application's Single sign-on page, SAML Certificates, App Federation Metadata Url | ## 1. Register the app 1. In the Microsoft Entra admin center, open Identity, then Applications, then App registrations, and press New registration. 2. Fill in the form: | Entra ID field | Value | |---|---| | Name | `Plotwork` | | Supported account types | Accounts in this organizational directory only (single tenant) | | Redirect URI | Platform Web, address `https://api.plotwork.dev/auth/sso/callback` | 3. Press Register. ## 2. Make a client secret 1. In the app registration, open Certificates & secrets, then Client secrets, and press New client secret. 2. Give it a description and an expiry, and press Add. 3. Copy the Value at once: Entra ID shows it only now. The Secret ID is not the secret. Note the expiry. When the secret expires, sign-in stops; make a new one and paste it into Plotwork before then. ## 3. Send the address in the ID token 1. Open API permissions and check that Microsoft Graph has the delegated permissions `openid`, `email` and `profile`. Add the ones that are missing, and grant admin consent. 2. Open Token configuration, press Add optional claim, choose ID as the token type, tick `email`, and press Add. Entra ID puts the address in the `email` claim only for a person whose account has a mail address. An account with no mailbox has none, so for a token from your tenant with no `email` claim, Plotwork reads the user principal name instead: the `upn` claim, then `preferred_username`. Either way the address must be on a domain your organisation verified in Plotwork. ## 4. Copy the values into Plotwork 1. On the app registration's Overview, copy the Application (client) ID and the Directory (tenant) ID. 2. In Plotwork, open your organisation's Single sign-on tab and fill in Identity provider: | Plotwork field | Value | |---|---| | Issuer URL | `https://login.microsoftonline.com/<Directory (tenant) ID>/v2.0` | | Client ID | The Application (client) ID | | Client secret | The Value you copied | 3. Press Save provider, then turn on Sign in with single sign-on. Plotwork refuses the shared issuers `/common`, `/organizations` and `/consumers`: they would let any Microsoft tenant's people in. Use your own tenant's issuer, as above. ## Or connect with SAML 2.0 1. In the Microsoft Entra admin center, open Identity, then Applications, then Enterprise applications, press New application, then Create your own application. Name it `Plotwork`, choose Integrate any other application you don't find in the gallery (Non-gallery), and press Create. 2. Open Single sign-on and choose SAML. 3. In Plotwork's Single sign-on tab, choose SAML 2.0 under Identity provider, then edit Basic SAML Configuration in Entra ID: | Entra ID field | Value | |---|---| | Identifier (Entity ID) | The Entity ID from Plotwork. Make it the default. | | Reply URL (Assertion Consumer Service URL) | The ACS URL from Plotwork | | Sign on URL, Relay State, Logout Url | Leave empty | 4. Under Attributes & Claims, edit Unique User Identifier (Name ID): set Name identifier format to Email address and Source attribute to `user.mail`. If your people's mail is empty and their user principal name is their address, use `user.userprincipalname` instead. 5. Under SAML Certificates, keep Signing Option at Sign SAML assertion and Signing Algorithm at SHA-256, and copy the App Federation Metadata Url. 6. In Plotwork, paste it under Metadata URL, leave Email attribute empty, and press Save provider. Then turn on Sign in with single sign-on. 7. Under Users and groups, assign the people or groups who should sign in. If you keep a Name ID that is not an address, put `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` under Email attribute in Plotwork: Entra ID sends the mail address in that claim by default. Entra ID rotates the signing certificate before it expires. When it does, save the provider in Plotwork again with the same metadata URL. ## Provisioning with SCIM 1. In Plotwork's Single sign-on tab, under Provisioning with SCIM, press Make token and copy the token and the SCIM base URL. 2. In the Microsoft Entra admin center, open the Plotwork enterprise application (for an OIDC app registration, make a Non-gallery enterprise application for provisioning), then Provisioning, and press Get started. 3. Set Provisioning Mode to Automatic and fill in Admin Credentials: | Entra ID field | Value | |---|---| | Tenant URL | The SCIM base URL from Plotwork | | Secret Token | The token from Plotwork | 4. Press Test Connection, then Save. 5. Under Mappings, open Provision Microsoft Entra ID Users and check that `userPrincipalName` (or `mail`) maps to `userName` and `Switch([IsSoftDeleted], , "False", "True", "True", "False")` maps to `active`. Remove mappings to attributes Plotwork does not keep if you like: it ignores them. 6. Under Users and groups, assign the people and groups to provision, set Provisioning Status to On, and save. Entra ID runs a cycle about every 40 minutes; Provision on demand runs one person at once. 7. In Plotwork, choose the role each pushed group gives under Groups and roles. ## Troubleshooting | What you see | What to check | |---|---| | "The issuer could not be used: the issuer names every tenant" | Replace `common` or `organizations` in the issuer with your Directory (tenant) ID. | | "Your address is not on a domain your organisation verified" | The person's mail address in Entra ID, or their user principal name when they have no mail address, is on another domain. Check the optional claim in step 3. | | "Your identity provider sent a sign-in Plotwork could not trust" | The Client ID in Plotwork is not this app's, or the issuer is another tenant's, or the token named no address at all. Check that `openid`, `email` and `profile` are granted in step 3. | | Sign-in stopped working one day | The client secret expired. Make a new one and save it in Plotwork. For SAML, the signing certificate rotated: save the provider again with the same metadata URL. | | Provisioning logs show 400 for a user | The `userName` Entra ID sends is not an address on a domain you verified in Plotwork. Map `mail` to `userName`, or verify the domain. | | "Your identity provider sent a sign-in Plotwork could not trust" with SAML | The Identifier or the Reply URL is not Plotwork's, or token encryption is on under Token encryption. Turn it off. | ## See also | Page | Why | |---|---| | [Single sign-on](https://docs.plotwork.dev/single-sign-on.md) | Domains, the switches, Require single sign-on and the way back in | | [Single sign-on with Okta](https://docs.plotwork.dev/single-sign-on-okta.md) | The same for Okta | | [Single sign-on with Google Workspace](https://docs.plotwork.dev/single-sign-on-google-workspace.md) | The same for Google Workspace | --- # Single sign-on with Google Workspace > Page: https://docs.plotwork.dev/single-sign-on-google-workspace. Index of every page: https://docs.plotwork.dev/llms.txt Connect an Enterprise organisation to Google Workspace with OpenID Connect or SAML 2.0. Prove your email domain in Plotwork first, as the [single sign-on guide](https://docs.plotwork.dev/single-sign-on.md) describes. ## TL;DR | Plotwork field | Where it comes from in Google Cloud | |---|---| | Issuer URL | `https://accounts.google.com` | | Client ID | The OAuth client's Client ID, ending in `.apps.googleusercontent.com` | | Client secret | The OAuth client's Client secret | | Redirect URI to give Google | `https://api.plotwork.dev/auth/sso/callback`, under Authorized redirect URIs | | SAML: ACS URL and Entity ID to give Google | The ACS URL and the Entity ID on Plotwork's Single sign-on tab | | SAML: metadata | The custom SAML app's IdP metadata file, pasted into Plotwork | ## 1. Set up the consent screen 1. In the Google Cloud console, choose or create a project in your Workspace organisation. 2. Open APIs & Services, then OAuth consent screen (Google Auth Platform, Branding and Audience in newer consoles). 3. Set the user type, or Audience, to Internal, so only accounts in your Workspace can sign in. 4. Fill in the app name (`Plotwork`), the support email and the developer contact, and save. ## 2. Make the OAuth client 1. Open APIs & Services, then Credentials, press Create credentials, and choose OAuth client ID. 2. Fill in the form: | Google field | Value | |---|---| | Application type | Web application | | Name | `Plotwork` | | Authorized JavaScript origins | Leave empty | | Authorized redirect URIs | `https://api.plotwork.dev/auth/sso/callback` | 3. Press Create, and copy the Client ID and the Client secret. ## 3. Copy the values into Plotwork In Plotwork, open your organisation's Single sign-on tab and fill in Identity provider: | Plotwork field | Value | |---|---| | Issuer URL | `https://accounts.google.com` | | Client ID | The Client ID you copied | | Client secret | The Client secret you copied | Press Save provider, then turn on Sign in with single sign-on. ## How Plotwork tells your Workspace from others Every Google account signs in at the same issuer, so Plotwork checks two more things for Google. The ID token must carry the `hd` claim, which Google sets only for a Workspace account, and it must equal the domain of the person's address. The address must also be on a domain you verified in Plotwork. A personal Google account, or a Workspace account of another company, is refused even if it signs in at Google. ## Or connect with SAML 2.0 1. In the Google Admin console, open Apps, then Web and mobile apps, then Add app, then Add custom SAML app. Name it `Plotwork` and press Continue. 2. On Google Identity Provider details, press Download Metadata and keep the file. Press Continue. 3. In Plotwork's Single sign-on tab, choose SAML 2.0 under Identity provider, then fill in Google's Service provider details from it: | Google field | Value | |---|---| | ACS URL | The ACS URL from Plotwork | | Entity ID | The Entity ID from Plotwork | | Start URL | Leave empty | | Signed response | Either. Plotwork accepts a signed assertion or a signed response. | | Name ID format | EMAIL | | Name ID | Basic Information, Primary email | 4. Press Continue, skip Attribute mapping, and press Finish. 5. Open the app's User access and turn it on for everyone, or for the organisational units or groups who should sign in. Google can take a few minutes to apply it. 6. In Plotwork, open the metadata file you downloaded, paste all of it under Or paste the metadata XML, leave Email attribute empty, and press Save provider. Then turn on Sign in with single sign-on. Google's certificate for the app lasts five years. When you make a new one under Manage certificates, download the metadata again and paste it into Plotwork. With SAML, the address check is the same as above: only addresses on domains you verified in Plotwork sign in. ## Troubleshooting | What you see | What to check | |---|---| | "Your identity provider did not sign you in" | The consent screen is set to Internal and the person's account is outside your Workspace, or they cancelled. | | "Your address is not on a domain your organisation verified" | The person used a personal Google account, or an address on a domain you have not verified in Plotwork. | | Google says `redirect_uri_mismatch` | The redirect URI in the OAuth client is not exactly `https://api.plotwork.dev/auth/sso/callback`. | | Google says `app_not_configured_for_user` with SAML | User access is off for the person's organisational unit or group, or the change has not applied yet. | | "Your identity provider sent a sign-in Plotwork could not trust" with SAML | The ACS URL or the Entity ID at Google is not Plotwork's, or the certificate changed and the metadata in Plotwork is the old one. | ## See also | Page | Why | |---|---| | [Single sign-on](https://docs.plotwork.dev/single-sign-on.md) | Domains, the switches, Require single sign-on and the way back in | | [Single sign-on with Okta](https://docs.plotwork.dev/single-sign-on-okta.md) | The same for Okta | | [Single sign-on with Microsoft Entra ID](https://docs.plotwork.dev/single-sign-on-entra-id.md) | The same for Microsoft Entra ID | --- # The drift dashboard > Page: https://docs.plotwork.dev/drift-dashboard. Index of every page: https://docs.plotwork.dev/llms.txt The drift dashboard shows, in one place, which diagrams across an organisation's repositories still match the files they are drawn from. The GitHub Action finds drift in each repository by itself on every plan; with `report-drift` turned on, each run also sends what it found to the organisation's Drift tab in the portal. ## TL;DR | Question | Answer | |---|---| | How do I turn it on? | Give the Action's `generate` step `report-drift: true` and an organisation key in `PLOTWORK_API_KEY`. | | What is sent? | Metadata only: the repository, the ref, the commit, the event, the run and pull request links, and per diagram its name, path, status, change counts and CODEOWNERS owners. Never a diagram, a file, or the names of what changed. | | Where do I read it? | The organisation's Drift tab in the portal. Every member can read it, readers included. | | What narrows it? | Stale only (drifted or failed), and one owner or team, or the diagrams with no owner. | | Which plan? | Pro and Enterprise. On Free a report is refused with a notice and nothing is stored; the check itself still runs. | | How long is a run kept? | By the organisation owner's plan, in the table under [How long runs are kept](#4-how-long-runs-are-kept). | | Can it fail my build? | No. A refused or unsent report is a notice in the log; the check's own result decides the exit code. | ## 1. Turn on reporting 1. In the portal, open the organisation and choose the Keys tab. Make a key that can do diagrams ("Diagrams" or "Diagrams and the audit log"). A key made "Audit log only" cannot send a report, and a personal key is refused. 2. Store it as a repository or organisation secret named `PLOTWORK_API_KEY`. 3. Add `report-drift: true` to the step that runs `generate`, and pass the key to that step. ```yaml name: diagrams on: pull_request: push: branches: [main] permissions: contents: read jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: persist-credentials: false - uses: plotwork-official/plotwork-action@dcec4036897b73bd3ea724b3991f4fd0ac880757 # v1.0.0-beta.14 with: command: generate mode: check report-drift: true env: PLOTWORK_API_KEY: ${{ secrets.PLOTWORK_API_KEY }} ``` The samples pin the latest release of the Action by its commit. `@v1` becomes available at the first stable release. The run reports to the organisation named by the `rules-org` input, else by `brand.organization` in the repository's `plotwork.config.json`, else to the key's own organisation. Reporting works in check mode and in write mode. A pull request from a fork gets no secrets from GitHub Actions, so its run says the key is missing and reports nothing. When the report is stored, the run logs a notice, "Sent to the drift dashboard", with a link to the organisation's Drift tab. ## 2. What is sent The Action builds the report from the run's own result and leaves everything else out. The diagram is compared with its files on the machine that runs the job; only the outcome travels. | Sent | Not sent | |---|---| | The repository's `owner/name`, the git ref, the commit, the event | A diagram's source or picture | | Links to the run and to the pull request | A file's content, or a path a source reads | | For each diagram: its name from the settings, its output path, its status | The names or details of what was added, removed or changed | | How many items were added, removed and changed, and how many errors | The key, which goes only in a header | | Its owners, as CODEOWNERS writes them for the diagram and its sources | Anything about the person who pushed | A diagram's status is one of four: | Status | Meaning | |---|---| | In step | The diagram matches its files. | | Drifted | The files changed and the diagram did not. | | Written | The run wrote the diagram again. | | Failed | The run could not make the diagram. | One report holds up to 250 diagrams, and each diagram up to 20 owners. A repository with more diagrams splits its `generate` settings. Each run replaces what its repository showed before. A diagram taken out of the repository's settings leaves the dashboard with the next run. The organisation's [audit log](https://docs.plotwork.dev/audit-log.md) records that CI sent a report, with the repository and how many diagrams it checked. ## 3. Read the Drift tab Open the organisation in the portal and choose the Drift tab. Nothing on it changes anything; only CI writes to it. | Part | What it shows | |---|---| | Totals | Repositories, diagrams, drifted and failed, across every report, before any filter | | A repository card | Its name, when it was last checked, and links to its latest run and pull request | | A diagram | Its status as a mark and a word, its name and path, its change counts, and its owners | | Order | Failed and drifted diagrams first, so a stale one is never below an in-step one | Two filters narrow the list, and the tab says how many diagrams they leave: | Filter | Narrows to | |---|---| | Stale only | Diagrams that drifted or failed | | Owner or team | One owner from those the diagrams name, or the diagrams with no owner in CODEOWNERS | Show all clears both. Owners come from the repository's CODEOWNERS, so a team that owns the folder a diagram is written to, or the files it is drawn from, sees its diagrams by choosing its handle. ## 4. How long runs are kept The organisation owner's plan decides, and a daily clean-up removes older runs and statuses: | Plan | Drift dashboard | Runs kept | |---|---|---| | Free | No | Nothing is stored | | Pro | Yes | 90 days | | Enterprise | Yes | 365 days, or the days the contract sets | The dashboard goes with the organisation when it is deleted. ## 5. Plans The dashboard belongs to the organisation owner's plan, like the organisation's other limits. | The owner's plan | What happens | |---|---| | Free | The Action still checks every diagram and fails the pull request on drift. A report is refused with `plan.drift_dashboard_not_included`, logged as a notice, and nothing is stored. The Drift tab offers the owner the plans and tells everyone else the owner can upgrade. | | Pro or Enterprise | Reports are stored and shown. | | Moved down to Free | The tab stops showing reports at once, and the next daily clean-up removes what was stored. | ## See also | Page | Why | |---|---| | [Diagrams in CI](https://docs.plotwork.dev/diagrams-in-ci.md) | The Action's check and its inputs | | [Enterprise rollout](https://docs.plotwork.dev/enterprise-rollout.md) | Reusable workflows, required checks and CODEOWNERS across many repositories | | [Organisations](https://docs.plotwork.dev/organisations.md) | Roles, and the organisation keys a report needs | | [Audit log](https://docs.plotwork.dev/audit-log.md) | The record of each report CI sent | | [Plans and billing](https://docs.plotwork.dev/plans-and-billing.md) | What Pro and Enterprise include | | [Privacy policy](https://docs.plotwork.dev/privacy-policy.md) | What the dashboard stores, and for how long | --- # Repository settings > Page: https://docs.plotwork.dev/repository-settings. Index of every page: https://docs.plotwork.dev/llms.txt Keep how your team draws in one file, `plotwork.config.json`, at the root of the repository: defaults for the header options a diagram leaves out, style rules that CI enforces, the organisation whose brand hosted calls use, and the diagrams made from files of the repository. The command line, the GitHub Action and the local MCP server read the file; a hosted agent passes it as the `settings` argument. ## TL;DR | Question | Answer | |---|---| | Where does the file go? | `plotwork.config.json` at the repository root, or `.plotwork/config.json`. Plotwork looks in the diagram's folder (or the working folder) and each folder above it, up to the repository root. | | What can it hold? | `defaults` for header options, `rules` that CI checks, `brand` naming an organisation, and `sources`, the diagrams made from files (see [Diagrams as code](https://docs.plotwork.dev/diagrams-as-code.md)). Every section is optional. | | Does a diagram's header still win? | Yes. A default fills only an option the header does not write. | | Can my editor complete it? | Yes. Add `"$schema": "https://docs.plotwork.dev/schemas/plotwork.config.json"`. | | What fails CI? | A broken rule, unless the rule says `"level": "warning"`. Defaults never fail anything. | | Which plan? | The file is free on every plan and needs no account. Saving rules in an organisation, and a brand, are part of Pro. | | Is anything sent? | Not by default. The file is read on the machine that runs Plotwork. Its brand colors are never in it: `brand` is only a name. | ## 1. Write the file ```json { "$schema": "https://docs.plotwork.dev/schemas/plotwork.config.json", "defaults": { "icons": "auto", "direction": "right", "legend": "off", "theme": "light", "embedSource": true, "kinds": { "mindmap": { "layout": "radial" }, "tree": { "style": "org" } } }, "rules": { "requireDescription": true, "ids": { "pattern": "^[a-z][a-z0-9_]*$", "hint": "Use snake_case ids." }, "icons": [ { "label": "postgres|mysql", "use": [ "aws:rds", "simple:postgresql", "simple:mysql" ] } ], "tones": [ { "types": [ "database" ], "allow": [ "green", "neutral" ] } ] }, "brand": { "organization": "acme" } } ``` A file that does not check is reported with its line and column (S100), and nothing is drawn or checked with it. ## 2. Defaults A default fills a header option the diagram leaves out. The header of a diagram always wins, and a default a kind's header does not take is left out for that kind. | Field | What it sets | Where it applies | |---|---|---| | `icons` | `icons=auto` or `icons=off`: whether an item with no `icon=` gets the icon its label names. | Every kind whose header takes `icons=` | | `direction` | `direction=` (`auto`, `right`, `down`, `left`, `up`). | Every kind whose header takes the value | | `legend` | `legend=on` or `legend=off`. | Flow | | `animate`, `numbered` | `animate=` and `numbered=`, `on` or `off`. | The kinds that take them | | `kinds` | Header options for one kind, such as `{ "mindmap": { "layout": "radial" } }`. A value the kind refuses is left out with warning S200. | That kind | | `theme` | The color scheme a single picture is drawn in: `light`, `dark` or `auto`. | The command line's `render` | | `schemes` | The pictures written beside a diagram: `light-and-dark` (the default) writes a light and a dark SVG; `auto` writes one SVG that follows the reader's light or dark setting, which GitHub shows right in dark mode. A source of `sources` can set its own. | The command line's `sync` and `generate`, the GitHub Action's `sync` and `generate`, and the `sync_markdown` tool | | `embedSource` | Whether a saved SVG carries its source. | The command line's `render`, the GitHub Action's `render`, and the drawing tools (true turns `embedSource` on). Never publishing: a published picture carries its source only when the call asks. | The pictures, the source an SVG carries, and a drawing's view link include the defaults, so a picture reads back the same without the file. A change to the defaults makes the pictures under every diagram fence stale, and `sync` redraws them. ## 3. Rules Rules are checked by `plotwork check`, by the GitHub Action's `check` and `validate`, and by the drawing tools, which return a broken rule as a warning and never stop a drawing. | Field | What it checks | Finding | |---|---|---| | `requireTitle` | Every diagram has a title in its header. | R207 | | `requireDescription` | Every diagram has `desc=` in its header, the text a screen reader announces. | R200 | | `maxNodes` | No diagram has more nodes than this. | R201 | | `forbidKinds` | No diagram is of these kinds. | R202 | | `ids` | Every node id matches `pattern`. | R203 | | `labels` | Every node label matches `pattern`. | R204 | | `icons` | Every node whose label matches `label` (case ignored) draws one of the icons in `use`. `aws:*` accepts any icon of a set. | R205 | | `tones` | Nodes of `types` (or every node) take only the tones in `allow`, or none of those in `forbid`. | R206 | | `level` | At the top of `rules`, the level of every rule; on a rule, its own. `error` (the default) or `warning`. | | | `kinds` | On a naming, icon or tone rule: only diagrams of these kinds. | | | `hint` | On a naming, icon or tone rule: what to tell the author. | | A pattern is a JavaScript regular expression without the slashes. One that repeats a group that itself repeats, such as `(a+)+`, or uses a back reference, is refused (R101), because it can take too long to match. An icon must be one Plotwork bundles; find its name with `npx @buun_group/plotwork icons postgres`. Node rules look at the parts each kind calls a node: the nodes of a flow, the actors of a sequence, the tables of a schema, the elements of a C4 view, the classes of a class diagram (never asked for an icon), the states of a state diagram, the blocks of a layered architecture, the lanes and items of a swimlane, the topics of a mind map, and the entries of a tree. A kind without nodes, such as a chart or a timeline, is held to the rules about the whole diagram only. A mind map topic and a tree entry have no id, so an `ids` rule passes them. ## 4. Brand `brand.organization` names the organisation (its slug or id) whose saved brand colors hosted calls use. It takes effect when the caller is a member of that organisation and its owner's plan includes brand themes; otherwise the call draws as it would without it. The file never holds colors: a brand is saved in the organisation by its owner or an admin. See [Brand themes](https://docs.plotwork.dev/brand-themes.md). ## 5. Sources `sources` lists the diagrams made from files of the repository: for each, the files (`from`), their `format`, and where the diagram goes (`out`). `plotwork generate` and the Action's `command: generate` read it; every other command leaves it alone. The fields, the overrides file each diagram may have, and the workflows are in [Diagrams as code](https://docs.plotwork.dev/diagrams-as-code.md). ## 6. Where the file is read | Host | How it gets the settings | |---|---| | Command line | Every command reads the file from the diagram's folder up to the repository root. `--config <file>` or `PLOTWORK_CONFIG` names another. `check` needs a `rules` section. | | GitHub Action | Reads one settings file per run: the first found from `working-directory` up to the repository root. A settings file in a subfolder is not read; run the Action once per folder, with `working-directory`, if folders need their own. `config` names another file, or `off` reads none. | | Local MCP server | Reads the file once at start, from the folder it starts in up to the repository root, or the file `PLOTWORK_CONFIG` names, and gives it to every drawing tool whose call passes no `settings`. | | Hosted MCP server and REST API | Cannot read a repository. An agent reads the file and passes it as `settings` to `draw_diagram`, `validate_diagram`, `render_diagram`, `outline_diagram`, `edit_diagram`, `draw_from_pattern`, `convert_mermaid`, `sync_markdown` and `publish_diagram`, so a published picture follows the defaults too. Settings that do not check are refused with the path of each problem. A call made with an organisation's API key also gets the organisation's saved rules as warnings, unless it brings rules of its own. | ## 7. Check in CI ```bash npx @buun_group/plotwork check docs README.md ``` `check` reads every Markdown and `.diagram` file the words name, writes the defaults into each header, and checks the rules. It exits with 1 when a rule at level error is broken or a diagram has an error, and with 2 when there are no rules or the settings do not check. ```yaml - uses: actions/checkout@v7 - uses: plotwork-official/plotwork-action@dcec4036897b73bd3ea724b3991f4fd0ac880757 # v1.0.0-beta.14 with: command: check ``` The samples pin the latest release of the Action by its commit. `@v1` becomes available at the first stable release. The Action finds `plotwork.config.json` by itself. Every finding is an annotation on its line of the pull request: an error for a rule at level error, a warning otherwise. ## 8. Share rules across repositories On Pro, an owner or admin saves rules in the organisation with [`PUT /orgs/{org}/style-rules`](https://docs.plotwork.dev/api/put-orgs-org-style-rules.md), and CI in every repository applies the same ones with an organisation API key. The saved rules take the fields of the `rules` section, with `"version": 1`. ```yaml - uses: plotwork-official/plotwork-action@dcec4036897b73bd3ea724b3991f4fd0ac880757 # v1.0.0-beta.14 with: command: check rules-org: acme env: PLOTWORK_API_KEY: ${{ secrets.PLOTWORK_API_KEY }} ``` An agent connected to the hosted MCP server gets the same rules back as warnings when it calls with the organisation's key, or as a member whose `settings` name the organisation in `brand.organization`. On your machine, `npx @buun_group/plotwork check docs --org acme` with `PLOTWORK_API_KEY` set does the same. The organisation's rules replace the file's rules; the file's defaults still apply. The key is never printed. If the owner moves to Free, the saved rules are kept but CI is told they no longer apply. ## 9. Privacy The file stays where it is: Plotwork reads it on the machine that runs the command line, the Action or the local server, and fetches nothing it names. Settings an agent passes to a hosted tool are worked on in memory with the diagram and are not stored. Rules saved in an organisation are stored as the text typed, with who saved them and when. See the [privacy policy](https://docs.plotwork.dev/privacy-policy.md). ## See also | Doc | Why | |---|---| | [Keep diagrams in sync in CI](https://docs.plotwork.dev/diagrams-in-ci.md) | The GitHub Action's other inputs, and checking that pictures are up to date | | [Diagrams as code](https://docs.plotwork.dev/diagrams-as-code.md) | The `sources` section, overrides, and drift | | [Brand themes](https://docs.plotwork.dev/brand-themes.md) | What a brand changes, and how an organisation saves one | | [Organisations](https://docs.plotwork.dev/organisations.md) | Roles, members and organisation API keys | | [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md) | The header options the defaults fill | | [Privacy policy](https://docs.plotwork.dev/privacy-policy.md) | What is stored about an organisation and its rules | --- # Enterprise support > Page: https://docs.plotwork.dev/enterprise-support. Index of every page: https://docs.plotwork.dev/llms.txt An organisation on an Enterprise contract has priority support: a named person at Plotwork who answers it, a first response target in business hours, and the hours support is staffed. Every member finds them on the organisation's Support page in the portal, and asks for help from the same page. ## TL;DR | Question | Answer | |---|---| | Where is it? | In the portal, open the organisation and choose Support, at the bottom of the sidebar. Every member of an Enterprise organisation sees it, readers included. | | Who answers? | The named contact on the page: a Plotwork person, by name and email address. Write to them directly, or use the form on the page. | | How fast? | The page says it, for example "First response within 4 business hours". That is the time to a first human answer, counted in the support hours your contract names. | | How do I ask? | Fill in Ask for help: a subject, a severity and a message. The contact gets it by email at once, with your address to reply to, and answers you by email. | | Which severity? | Question, Something is broken, or Urgent: production affected. Urgent requests are read first. | | What does it cost? | Nothing more. Priority support is part of the Enterprise contract. | | Not on Enterprise? | The owner and the admins see what Enterprise adds, with a way to talk to us. Members see no Support item. | | Is what I write kept? | Yes, for 365 days, as you wrote it. See [What is kept](#5-what-is-kept). | ## 1. Find the Support page Open the organisation in the portal at [app.plotwork.dev](https://app.plotwork.dev) and choose Support, the last item in the sidebar. The page has three parts: | Part | What it shows | |---|---| | Your named contact | The person's name, their email address with a copy button, and a link that opens a new email to them | | What priority support means here | The first response target, the support hours, and the uptime commitment. The owner and the admins also see the contract's start and end days and how it renews. | | Ask for help | The form that sends a request to the named contact | The page never shows the contract's price, its billing contact or its invoices, to anyone. Those stay between your billing contact and Plotwork. ## 2. Your named contact The contact is the Plotwork person your contract names. A request from the form reaches them, and so does an email you write to them yourself. When the page says Plotwork is naming your contact, a request from the form still reaches the support team. | You want to | Do this | |---|---| | Ask a question with context from the page | Use Ask for help. The email names your organisation, your role and the severity, so nothing needs repeating. | | Send a screenshot, a log or a file | Write to the contact by email and attach it. The form takes text only. | | Follow up on a request | Reply to the contact's answer. The thread stays in your inbox and theirs. | ## 3. Ask for help | Field | What to write | |---|---| | Subject | One line on what it is about, up to 160 characters, such as "Drift checks fail on payments-api". | | Severity | How urgent it is (below). | | Message | What happened, what you expected, and anything that helps reproduce it: the repository, the diagram, the time. Up to 5000 characters. Line breaks are kept. | | Severity | Choose it when | |---|---| | Question | You want to know how to do something, or whether Plotwork can. | | Something is broken | Something does not work as it should, and you can still work. | | Urgent: production affected | Plotwork is stopping work that matters now: a check blocks every merge, hosted diagrams in a live page fail, or nobody can sign in. | When it is sent, the page says who will answer and how soon, for example "Sent. Mary Somerville will answer by email, first within 4 business hours." The answer comes by email to the address you sign in with. An organisation can send 20 requests a day from the form. Past that, the page says so and gives the contact's address to write to instead. ## 4. What the targets mean | Term | Meaning | |---|---| | First response | The time from your request to the first answer from a person, not an automatic reply. It is a target the contract sets, measured in business hours. | | Business hours | The support hours on the page, such as "Mon to Fri, 9:00 to 17:00 AEST". A request sent outside them counts from when they next begin. | | Support hours | When the named contact and the support team work on requests. The page shows them as your contract writes them. | | Uptime SLA | Plotwork's commitment on the availability of the hosted service. The level and the credits are in your contract. | A first response is not always the fix. It says what is known and what happens next. ## 5. What is kept | What | Kept | |---|---| | Your request: the organisation, your account and email address, the subject, the severity, the message and when you sent it | 365 days, then the daily clean-up deletes it. It goes sooner when the organisation or your account is deleted. | | Whether the email reached the contact, and when Plotwork marked the request answered | With the request | What you write is stored as you wrote it, so leave out a password, a key or anything you would not email. The [privacy policy](https://docs.plotwork.dev/privacy-policy.md) has the full account. ## See also | Page | Why | |---|---| | [Plans and billing](https://docs.plotwork.dev/plans-and-billing.md) | What Enterprise includes besides priority support | | [Organisations](https://docs.plotwork.dev/organisations.md) | Members, roles, and what each role can see | | [Single sign-on](https://docs.plotwork.dev/single-sign-on.md) | Another part of an Enterprise contract | | [Privacy policy](https://docs.plotwork.dev/privacy-policy.md) | What a support request stores, and for how long | --- # Plans and billing > Page: https://docs.plotwork.dev/plans-and-billing. Index of every page: https://docs.plotwork.dev/llms.txt See what Free and Pro include, how to upgrade and cancel, and what the daily call limit does when you reach it. ## TL;DR | Question | Answer | |---|---| | What plans are there? | Free and Pro. Drawing diagrams is free on both. The plans differ in the daily call limit, in whether you may call the REST API to draw and check diagrams, in how many hosted diagrams you keep and for how long, and in how many organisations you can own and how many people each holds. | | What does Pro cost? | $9 a month, in US dollars, billed monthly by Stripe. | | How do I upgrade? | Open the Plan page of the portal and choose Upgrade to Pro. You pay on a Stripe page. | | How do I cancel? | Choose Cancel Pro on the Plan page. Pro stays until the end of the period you paid for, and Keep Pro takes the cancellation back. | | How do I change my card or read a receipt? | Choose Manage billing on the Plan page to change your card. Receipts and invoices are listed on the same page, and each downloads as a PDF. | | Can I use the REST API on Free? | You can publish a diagram over REST on every plan. The portal and the hosted MCP server do everything else, so a free account draws through an agent. Calling the REST API directly to draw, validate, render, outline or convert a diagram, or to look up icons, needs Pro. | | What is the daily limit? | Free allows 300 calls a day and Pro allows 50,000. The count is per account and starts again at 00:00 UTC (Coordinated Universal Time). | | What happens at the limit? | The API answers HTTP 429 with the error code `usage.daily_limit` until 00:00 UTC. Nothing is billed for the calls it refuses. | | What if a payment fails? | Stripe retries it, and Pro stays on while it does. | | Where do I see my usage? | On the Usage page of the portal, and from `GET /user/usage` while you are signed in. | ## 1. What each plan includes Every number is per account. The API holds your account to exactly these numbers, and the pricing page on the Plotwork website shows the same ones. | Plan | Price | Who it is for | |---|---|---| | Free | Free | Everything you need to draw diagrams with an agent, and a few shareable links. | | Pro | $9 a month | For agents that run all day, and diagrams that should stay where you put them. | | Enterprise | Custom, annual contract | For platform teams that keep diagrams in CI across many repositories, with limits written for you. | | Usage | Free | Pro | Enterprise | |---|---|---|---| | Calls a day (REST API and MCP tool calls together) | 300 | 50,000 | Custom | | The REST API for drawing and checking (Publishing, the portal and the hosted MCP server work on every plan) | Not included | Included | Included | | PDF and PowerPoint downloads (PNG, SVG and presenting step by step are on every plan) | Not included | Included | Included | | Diagram editor (Edit a published diagram in the portal, keep its link, and restore a previous version) | Not included | Included | Included | | Hosting | Free | Pro | Enterprise | |---|---|---|---| | Hosted diagrams | 25 | 2,000 | Custom | | Storage | 10 MB | 1 GB | Custom | | Largest diagram | 2 MB | 2 MB | Custom | | Kept after you last publish | 30 days | Until you delete it | Custom | | Links that always show the latest version (Named links that move to each new version you publish) | 3 | 500 | Custom | | Organisations | Free | Pro | Enterprise | |---|---|---|---| | Organisations you can own | 1 | 5 | Custom | | People in one organisation (Owner, readers and pending invitations included) | 3 | 25 | Custom | | Brand themes (The organisation's colors on every diagram the hosted service draws) | Not included | Included | Included | | Saved style rules (The same rules for CI in every repository; a rules section in your own settings file is free) | Not included | Included | Included | | Members publish only to the library (No member's diagram at a public link; sending them to the library by default is on every plan) | Not included | Included | Included | | Contract | Free | Pro | Enterprise | |---|---|---|---| | Single sign-on with OpenID Connect (Okta, Microsoft Entra ID and Google Workspace) | Not included | Not included | Included | | Single sign-on with SAML | Not included | Not included | Included | | SCIM provisioning | Not included | Not included | Included | | Limits written for you | Not included | Not included | Included | | Uptime SLA | Not included | Not included | Included | | Priority support | Not included | Not included | Included | | Annual invoicing | Not included | Not included | Included | | Data processing agreement | Not included | Not included | Included | | Audit log export | Not included | Not included | Included | | Audit events and drift sent to your tools (Datadog, Dynatrace, Splunk, Microsoft Sentinel or a signed webhook) | Not included | Not included | Included | | Self-hosting licence (The engine inside your own network, for regulated companies) | Not included | Not included | Add-on | | Included on every plan | Free | Pro | Enterprise | |---|---|---|---| | Every diagram tool | Included | Included | Included | | The local server | Included | Included | Included | | The hosted MCP server | Included | Included | Included | | Publishing over the REST API | Included | Included | Included | | The portal | Included | Included | Included | Both plans include every diagram tool. Pro includes everything in Free with higher limits. The local server, which you start with `npx`, makes no network requests by default, needs no account and has no limit. A plan applies to the hosted MCP server and to the REST API. Publishing a diagram over the REST API, the portal and the hosted MCP server work on every plan, while calling the drawing and checking routes of the REST API directly needs a plan that includes it, which is Pro. The plan belongs to your account, so every API key and every connected agent on the account shares its limits. Hosted diagrams are public links to the pictures of your diagrams. [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md) explains how to publish one, who can see it and how long it is kept. An organisation is a private library that a team shares, and it has the plan of the account that owns it. A Pro owner gives the whole organisation the Pro limits, a Free owner gives it the Free ones, and a member's own plan changes nothing for it. The organisation keeps its own pool of diagrams, apart from the owner's personal ones. [Organisations](https://docs.plotwork.dev/organisations.md) lists the limits that apply and explains roles, invitations and keys. ## 2. Upgrade to Pro 1. Sign in at [app.plotwork.dev](https://app.plotwork.dev) and open the Plan page. 2. Choose Upgrade to Pro. The browser goes to a Stripe page. 3. Pay there. Stripe takes the payment, so Plotwork never sees or stores your card details. 4. You return to the Plan page, which confirms the payment. It says Pro is active once your plan has updated. Your new limits apply within a minute of the payment going through. Diagrams you already host stop expiring at once. ## 3. Manage billing and cancel The Plan page shows your plan, its status and the date your current period renews or ends. Choose Manage billing to open the Stripe Billing Portal in the same tab, where you can change your card. Cancelling and reading your receipts are done on the Plan page itself. | You do this | What happens | |---|---| | Cancel | Choose Cancel Pro and confirm. You can say why, which is optional. Pro stays until the end of the period you paid for. The Plan page then says when it cancels. After that date your account moves to Free. | | Change your mind before that date | Choose Keep Pro on the Plan page. The subscription renews as before. | | Read or download a receipt | The Plan page lists every receipt and invoice. Open one to read it in the page, or download it as a PDF. | | Let Pro end | Your account moves to Free. Nothing is deleted at once: each hosted diagram gets a full 30 days from the change, and you can delete any of them sooner on the Diagrams page. | | Upgrade again later | Choose Upgrade to Pro on the Plan page again. | An account that has never been on Pro has no billing account to manage yet. Upgrade to Pro first. ## 4. The daily call limit The limit counts calls to your account across the hosted MCP server and the REST API. | Counts toward the limit | Does not count | |---|---| | Every REST API call made with an API key, including publishing a diagram | Connecting to the MCP server | | Every MCP tool call, including `publish_diagram` | Listing the tools | | Calls from every API key and every connected agent on the account together | Signing in, and reading your usage | The count is for one UTC day. It starts again at 00:00 UTC, which is the same moment for every account. A separate per-minute limit applies to each key. It protects the service, and it does not depend on your plan. Wait a minute and call again when you reach it. ### The 429 response When all of the day's calls are spent, the API answers HTTP 429 with `usage.daily_limit` as its `errorCode`. The `retry-after` header gives the seconds until 00:00 UTC, and the `ratelimit` header gives what is left and the same time. ```text HTTP/1.1 429 Too Many Requests retry-after: 3600 ratelimit: "daily";r=0;t=3600 ``` ```json { "error": true, "errorCode": "usage.daily_limit", "message": "This account has used all 300 calls for today. The count resets at 00:00 UTC.", "details": { "limit": 300, "used": 300, "resetsAt": 1791590400000, "retryAfterSeconds": 3600 }, "requestId": "7c1d0a3e-5f2b-4b6e-8a90-3d4e5f6a7b8c" } ``` The example is a Free account. A Pro account sees its own `limit`. The MCP server answers a spent limit with HTTP 429 and a JSON-RPC error with code -32029, and `usage.daily_limit` in the error data. Calls over the limit are refused, not billed. You can wait for 00:00 UTC, or upgrade to Pro for a higher limit. ### The REST API on the Free plan Every plan may publish a diagram over REST, and every plan has the portal and the hosted MCP server, which do the same drawing and checking. On the Free plan, an API key sent to a drawing or checking route of the REST API (`POST /v1/diagrams/draw` and the other `/v1` routes, except publishing) is refused with HTTP 403 and the error code `plan.rest_not_included`. A refused call is not counted toward the daily limit. Use the hosted MCP server, or upgrade to Pro. ## 5. When a payment fails Stripe retries a failed payment. While it does, Pro stays on, and the Plan page says that your last payment failed. Update your card in Manage billing to keep Pro. If no payment goes through and the subscription ends, your account moves to Free, and your hosted diagrams follow the same rule as when Pro ends. ## 6. See your usage | Where | What it shows | |---|---| | The Usage page at [app.plotwork.dev/usage](https://app.plotwork.dev/usage) | Today's count against your limit, when it resets, and the last 30 days. | | The Plan page | Your plan, and a summary of what you use. | | The Diagrams page | How many hosted diagrams and how much storage you have used, against your plan. | | [`GET /user/usage`](https://docs.plotwork.dev/api/get-user-usage.md) | The same usage as JSON, while you are signed in. | Reading your usage is not counted toward the limit. ## See also | Doc | Why | |---|---| | [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md) | Publish a diagram, share the link and delete it | | [Organisations](https://docs.plotwork.dev/organisations.md) | Share a private library with a team, and see whose plan it uses | | [Connecting an agent](https://docs.plotwork.dev/mcp/connect.md) | Connect an agent to the hosted server with a key or a browser sign-in | | [MCP server](https://docs.plotwork.dev/mcp/overview.md) | What the tools do, and how the local and the hosted server differ | | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Privacy policy](https://docs.plotwork.dev/privacy-policy.md) | What is stored about your account, your diagrams and your payments | --- # Cloud architecture > Page: https://docs.plotwork.dev/cloud-architecture. Index of every page: https://docs.plotwork.dev/llms.txt How to draw an AWS, Azure, or Google Cloud architecture that reads like a cloud architecture: typed groups for accounts, regions, and networks, a product icon on every service, and technology marks where they help. It is written for an AI agent first, so each step names the tool to call and says what a warning means. A person can follow the same steps by hand. This page holds the recipe; the pages in the Cloud architecture section hold the details, each with pictures drawn by the same renderer a diagram uses. ## TL;DR | Question | Answer | |---|---| | What do I write first? | `flow "Title" provider=azure direction=down`. The provider on the header sets the icons of every group. | | How do I draw a region, a network, or a subnet? | A `group` with `type=region`, `type=network`, or `type=subnet`. The type picks the icon, the tone, and a solid, dashed, or dotted frame. See [Group types](https://docs.plotwork.dev/cloud-architecture/group-types.md) for all 30, each drawn. | | How do I draw a service? | One `node` per service, with `icon=<provider>:<product>`, for example `icon=azure:cosmos-db`. | | How do I find the icon? | Call `map_icons` with every product name from the request, or `search_icons` for one name, and use the first result. Never invent a name. See [Icon names and lookup](https://docs.plotwork.dev/cloud-architecture/icon-names.md). | | Where do I see an icon before I use it? | On the icon pages: [AWS](https://docs.plotwork.dev/cloud-icons/aws.md), [Azure](https://docs.plotwork.dev/cloud-icons/azure.md), [Google Cloud](https://docs.plotwork.dev/cloud-icons/google-cloud.md), [other providers](https://docs.plotwork.dev/cloud-icons/other-providers.md), and [generic and technology icons](https://docs.plotwork.dev/cloud-icons/generic.md). Each icon is drawn above its name. | | How do I mark a technology? | `stack=simple:angular,simple:typescript` on the node, up to three marks. See [Marks, second lines, and overlays](https://docs.plotwork.dev/cloud-architecture/marks-and-overlays.md). | | How do I show an event or a webhook? | `a ..> b : "event"`, a dotted line with an arrowhead. Every line style is drawn on [Frames, tones, lines, and symbols](https://docs.plotwork.dev/cloud-architecture/lines-and-symbols.md). | | How do I show a scaling group across two zones? | An `overlay` line after the nodes: `overlay set "Web machines" type=scale-group members=w1,w2`. | | Is there a starting point? | Yes. `find_pattern` returns checked, ready-made diagrams, such as a three-tier network or a two-zone network. See the [architecture patterns guide](https://docs.plotwork.dev/architecture-patterns.md) and the [examples](https://docs.plotwork.dev/cloud-architecture/examples.md). | | What if I miss something? | `draw_diagram` and `validate_diagram` return warnings with the exact fix: `W204` for a node icon, `W205` for a group type. | | How big can it be? | About 15 nodes, and one more for each group, up to 25. Split a larger system by plane: network, data, delivery. | ## 1. The five-step recipe Follow these steps in order. Each one ends with a check. If the request matches a common architecture, such as a three-tier network or a serverless API, call `find_pattern` first and adapt the pattern it returns: the groups, icons, and warnings are already right, and steps 2 to 5 become a check of the renamed result. See the [architecture patterns guide](https://docs.plotwork.dev/architecture-patterns.md). 1. **Pick the provider.** Put `provider=aws`, `provider=azure`, or `provider=gcp` on the header. For a multi-cloud diagram, leave it off the header and put `provider=` on the top group of each cloud; nested groups inherit it. 2. **Build the group tree from the top down.** Write the outermost container first, then what it holds, giving every group a `type=`. A typical AWS tree is account, region, network, subnets. Name each group the way the provider does: `eu-west-1`, `Application VPC`, `Private subnets`. The [group types](https://docs.plotwork.dev/cloud-architecture/group-types.md) page lists the closed set of types. 3. **Add one node per service.** A service is a thing in the request: a load balancer, a database, a queue. One node for the service, not one per instance. Label the node with the product name next to its icon. 4. **Map every service to its icon.** Call `map_icons` once with the product names exactly as the request writes them, or `search_icons` for one name, take the first result, and write it as `icon=`. Then call `validate_diagram` (or `draw_diagram`): an unknown name is an error with the nearest valid one. 5. **Read the warnings.** `validate_diagram` reports a node whose label names a product but has no icon (`W204`), and a group whose name matches a type but has none (`W205`). Each gives the exact text to add. Add it, or write `icon=none` on a node that should stay plain, and validate again until no warning is left. A session for the request "a function that resizes images stored in S3" looks like this: | Call | Input | What comes back | |---|---|---| | `get_diagram_syntax` | `kind` set to `flow` | Group types, node options, arrows, and a complete cloud architecture example | | `map_icons` | `["Lambda", "S3"]` with `provider` set to `aws` | `aws:lambda` and `aws:s3`, each with a confidence | | `search_icons` | `lambda` | `aws:lambda` first. Use it for one name when `map_icons` is more than you need. | | `validate_diagram` | the source | `valid: true`, or errors with a line, a column, and a hint | | `outline_diagram` | the source | The diagram as text, to check against the request | | `render_diagram` | the source | The SVG | | `draw_diagram` | the source | The three calls above in one: warnings, outline, and the SVG in both themes | ## 2. Where each detail lives Each page below does one job and shows every item it names drawn by the renderer, with the exact source beside the picture, so you can copy what you see. | Page | What it holds | Read it when | |---|---|---| | [Group types](https://docs.plotwork.dev/cloud-architecture/group-types.md) | The closed list of `type=` values, each drawn, and the icon each type draws for each provider | You choose a type for a group | | [Frames, tones, lines, and symbols](https://docs.plotwork.dev/cloud-architecture/lines-and-symbols.md) | Frame styles, tones in light and dark, nesting depth, every edge style and arrowhead, labels, and the marks a node or group can carry | You want to know what a line or a color means | | [Icon names and lookup](https://docs.plotwork.dev/cloud-architecture/icon-names.md) | How `provider:product` resolves, how search ranks, and how to map a product name to an icon | You have a product name and need the `icon=` | | [AWS icons](https://docs.plotwork.dev/cloud-icons/aws.md), [Azure icons](https://docs.plotwork.dev/cloud-icons/azure.md), [Google Cloud icons](https://docs.plotwork.dev/cloud-icons/google-cloud.md) | Every icon of the set, grouped by category, drawn, with its exact name and aliases | You want to see the icon before you use it | | [Other provider icons](https://docs.plotwork.dev/cloud-icons/other-providers.md) | Alibaba Cloud, Oracle Cloud Infrastructure, IBM Cloud, Cloudflare, DigitalOcean, and Vercel | The request names one of those providers | | [Generic and technology icons](https://docs.plotwork.dev/cloud-icons/generic.md) | General icons, technology marks, status icons, and the glyph of each kind of service | You need a mark for a technology or a status | | [Marks, second lines, and overlays](https://docs.plotwork.dev/cloud-architecture/marks-and-overlays.md) | `stack=` marks, event edges, `sub=`, and `overlay` | A service needs a technology mark or spans two zones | | [Examples](https://docs.plotwork.dev/cloud-architecture/examples.md) | A web app on Azure, a two-zone network on AWS, and a serverless API on Google Cloud | You want a complete diagram to read | ## 3. Do and do not | Do | Do not | |---|---| | Call `map_icons` or `search_icons` for every product and use the first result | Invent an icon name; `validate_diagram` rejects it with the nearest valid one | | Use the product name from the request as the label, next to its icon | Label a node with only an icon, or with only a generic word such as `Database` | | Draw one node per service | Draw one node per instance, or one per subnet for the same service | | Give every group a `type=` and name it the way the provider does | Colour a group by hand, or use `type=trust-boundary` for a network | | Put the account, region, and network groups around the services, from the outside in | List the services flat and write the region in a label | | Set `provider=` once on the header | Repeat `provider=` on every group of a single-cloud diagram | | Add the exact text a `W204` or `W205` warning gives, or write `icon=none` | Ignore a suggestion without deciding | | Split a diagram that passes its node limit (15, plus one for each group, up to 25) by plane: network, data, delivery | Raise the node count by shrinking the text | ## See also | Doc | Why | |---|---| | [Group types](https://docs.plotwork.dev/cloud-architecture/group-types.md) | The 30 types of group, each drawn | | [Icon names and lookup](https://docs.plotwork.dev/cloud-architecture/icon-names.md) | From a product name to an `icon=` | | [Examples](https://docs.plotwork.dev/cloud-architecture/examples.md) | Three complete cloud diagrams | | [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md) | The whole language, kind by kind | | [Architecture patterns](https://docs.plotwork.dev/architecture-patterns.md) | Ready-made, checked diagrams to start from, each with slots to rename | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The validate-and-fix loop an agent follows | | [Language reference](https://docs.plotwork.dev/reference/language.md) | Every group type, node type, arrow, option, and diagnostic code | | [Icon reference](https://docs.plotwork.dev/reference/icons.md) | Every bundled icon and how search ranks them | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | `map_icons`, `search_icons`, `draw_diagram`, and the other tools | --- # Group types > Page: https://docs.plotwork.dev/cloud-architecture/group-types. Index of every page: https://docs.plotwork.dev/llms.txt The closed list of `type=` values a group can take, each drawn by the renderer as it appears in a diagram, with the frame and tone it uses and the icon it draws for each provider. It is for anyone choosing a type for a group, human or AI agent. ## TL;DR | Question | Answer | |---|---| | How many types are there? | 30. The list is closed: one spelling for each, no aliases. A misspelling is error `E124` with the nearest type. | | What does a type set? | The icon in the corner tab, the tone (color family), and the frame style (solid, dashed, or dotted). | | How do I pick one? | The type that says what the group is. A VPC is `network`, a public subnet is `public-subnet`, an Azure resource group is `resource-group`. | | What if no type fits? | Use `subnet`, `environment`, or `trust-boundary` when one is close, or write no type and set `icon=` and `tone=` by hand. | | Which icon does a provider draw? | The provider's own icon where it has one, the generic icon otherwise. The [icon matrix](#2-icons-by-provider) shows every cell. | | Can I change what a type sets? | Yes. `icon=` and `tone=` override it, and `icon=none` removes the icon. Prefer the type. | ## 1. The list Each picture is the group alone, drawn by the renderer with a plain node inside, with no provider set. The `type=` column is the text to write. The tone chip shows the tone in light and in dark. | Picture | `type=` | Use it for | Frame | Tone | |---|---|---|---|---| | | `cloud` | A provider's whole cloud: the outermost frame that holds its accounts, regions, and networks. | solid | neutral | | | `account` | An account, subscription, or project: the container of one provider's resources that bills and grants access together. | solid | neutral | | | `region` | A geographic region of a provider, such as eu-west-1 or West Europe. | dashed | neutral | | | `zone` | An availability zone inside a region, drawn as a column. | dashed | neutral | | | `network` | A VPC, VNet, or other private network that holds subnets and resources. | dashed | blue | | | `subnet` | A subnet inside a network whose role is not public, private, management, or isolated. | solid | neutral | | | `public-subnet` | A subnet whose resources can be reached from the internet, such as load balancers and bastion hosts. | solid | green | | | `private-subnet` | A subnet with no direct route from the internet, such as application servers and databases. | solid | violet | | | `management-subnet` | A subnet for administration: jump hosts, bastions, and tooling that operators use. | solid | amber | | | `isolated-subnet` | A subnet with no route in or out of the network, such as a database that only the application tier can reach. | solid | red | | | `resource-group` | A logical group of resources that are managed together, such as an Azure resource group. | solid | neutral | | | `cluster` | A Kubernetes or container cluster. | solid | violet | | | `scale-group` | An auto scaling group, scale set, or managed instance group of identical machines. | solid | aqua | | | `service-plan` | A hosting plan that runs several apps on shared compute, such as an App Service plan. | solid | violet | | | `web-tier` | The layer that faces users: web servers and the load balancer in front of them. | solid | blue | | | `app-tier` | The layer that runs application code: application servers, services, or functions. | solid | violet | | | `data-tier` | The layer that holds databases and caches. | solid | aqua | | | `storage-account` | A storage account or a set of buckets and file shares. | solid | amber | | | `messaging` | Queues, topics, and event buses that decouple services. | solid | orange | | | `edge` | The public edge in front of the application: DNS, CDN, and front doors. | solid | blue | | | `security` | Security services such as firewalls, web application firewalls, and key management. | solid | red | | | `identity-boundary` | The area that authenticates users and issues tokens: an identity provider and its directory. | dashed | violet | | | `trust-boundary` | A line across which trust changes: what is inside is checked differently from what is outside. | dashed | neutral | | | `observability` | Monitoring, logging, and tracing. | solid | aqua | | | `ci-cd` | A build and release pipeline with its repositories. | solid | violet | | | `environment` | A deployment environment such as development, staging, or production. | solid | amber | | | `cloud-boundary` | The line between your own premises and the cloud: everything inside is on the cloud side. | dotted | blue | | | `on-premises` | A data center or office network that you run yourself, outside any cloud. | dotted | neutral | | | `client-side` | Browsers, mobile apps, and devices that call the system. | dotted | neutral | | | `external-systems` | Third-party services and partners that you call but do not run. | dotted | neutral | ### Frame styles carry meaning The frame lets a reader tell a boundary from a container without reading the name. Dashed frames are boundaries (`region`, `zone`, `network`, `identity-boundary`, `trust-boundary`), dotted frames are one side of a line (`cloud-boundary`, `on-premises`, `client-side`, `external-systems`), and every other type is a solid container. [Frames, tones, lines, and symbols](https://docs.plotwork.dev/cloud-architecture/lines-and-symbols.md) draws each frame style, every tone, and the nesting depth. ### Tones carry meaning The four subnet roles each have their own tint, so a reader sees which side of the network a subnet is on before reading its name: `public-subnet` is green, `private-subnet` violet, `management-subnet` amber, and `isolated-subnet` red. `web-tier` is blue, `app-tier` violet, and `data-tier` aqua, so the three tiers of a request stay apart in the same way. Every tint keeps the text readable in light and in dark. A plain `subnet` stays neutral, for a subnet whose role is not one of the four. A group nested in another is shaded one step stronger than the one around it, in light and in dark, so an account, a region, a network, and a subnet read as four levels. Depth is automatic; there is nothing to write. ### The cloud and its boundary The provider's own cloud is the outermost frame, drawn with `type=cloud`. Draw a hybrid system with `type=cloud-boundary` around everything on the cloud side and `type=on-premises` beside it, joined by an edge through a gateway node, so the line between the two sides is the dotted frame. ## 2. Icons by provider With a provider, a type draws that provider's own icon where the provider has one. Where it has none, the type draws its generic icon, so a cell that repeats the generic icon means the provider has nothing more specific for that type. Set the provider once on the header (`flow "Title" provider=azure`), or on the top group of one cloud in a multi-cloud diagram; nested groups inherit it. Every cell shows the icon as the renderer draws it, and its name. The names are the exact text for `icon=` if you want to set one by hand. | `type=` | No provider | AWS | Azure | Google Cloud | |---|---|---|---|---| | `cloud` | `lucide:cloud` | `aws:cloud-group` | `azure:cloud` | `gcp:cloud` | | `account` | `lucide:cloud` | `aws:account` | `azure:subscriptions` | `gcp:cloud` | | `region` | `lucide:map-pin` | `aws:region` | `lucide:map-pin` | `lucide:map-pin` | | `zone` | `lucide:layers` | `lucide:layers` | `lucide:layers` | `lucide:layers` | | `network` | `lucide:network` | `aws:virtual-private-cloud-vpc` | `azure:virtual-network` | `gcp:vpc` | | `subnet` | `lucide:waypoints` | `lucide:waypoints` | `azure:subnet` | `lucide:waypoints` | | `public-subnet` | `lucide:lock-open` | `aws:public-subnet` | `lucide:lock-open` | `lucide:lock-open` | | `private-subnet` | `lucide:lock` | `aws:private-subnet` | `lucide:lock` | `lucide:lock` | | `management-subnet` | `lucide:settings` | `lucide:settings` | `lucide:settings` | `lucide:settings` | | `isolated-subnet` | `lucide:door-closed` | `lucide:door-closed` | `lucide:door-closed` | `lucide:door-closed` | | `resource-group` | `lucide:boxes` | `lucide:boxes` | `azure:resource-groups` | `lucide:boxes` | | `cluster` | `lucide:container` | `aws:eks` | `azure:aks` | `gcp:gke` | | `scale-group` | `lucide:server-cog` | `aws:auto-scaling-group` | `azure:vm-scale-sets` | `gcp:compute-engine` | | `service-plan` | `lucide:server` | `aws:ecs` | `azure:app-service-plans` | `gcp:cloud-run` | | `web-tier` | `lucide:monitor` | `lucide:monitor` | `lucide:monitor` | `lucide:monitor` | | `app-tier` | `lucide:server` | `lucide:server` | `lucide:server` | `lucide:server` | | `data-tier` | `lucide:database` | `aws:rds` | `azure:sql-database` | `gcp:cloud-sql` | | `storage-account` | `lucide:hard-drive` | `aws:s3` | `azure:blob-storage` | `gcp:cloud-storage` | | `messaging` | `lucide:send` | `aws:sqs` | `azure:service-bus` | `gcp:pubsub` | | `edge` | `lucide:globe` | `aws:cloudfront` | `azure:front-door` | `gcp:cloud-cdn` | | `security` | `lucide:shield-check` | `aws:waf` | `azure:firewall` | `gcp:security-identity` | | `identity-boundary` | `lucide:id-card` | `aws:iam` | `azure:entra-id` | `gcp:iam` | | `trust-boundary` | `lucide:shield` | `lucide:shield` | `lucide:shield` | `lucide:shield` | | `observability` | `lucide:activity` | `aws:cloudwatch` | `azure:monitor` | `gcp:observability` | | `ci-cd` | `lucide:workflow` | `aws:codepipeline` | `azure:devops` | `gcp:devops` | | `environment` | `lucide:rocket` | `lucide:rocket` | `lucide:rocket` | `lucide:rocket` | | `cloud-boundary` | `lucide:cable` | `lucide:cable` | `lucide:cable` | `lucide:cable` | | `on-premises` | `lucide:factory` | `aws:corporate-data-center` | `lucide:factory` | `lucide:factory` | | `client-side` | `lucide:monitor-smartphone` | `lucide:monitor-smartphone` | `lucide:monitor-smartphone` | `lucide:monitor-smartphone` | | `external-systems` | `lucide:plug` | `lucide:plug` | `lucide:plug` | `lucide:plug` | ## See also | Doc | Why | |---|---| | [Cloud architecture](https://docs.plotwork.dev/cloud-architecture.md) | The five-step recipe and a map of these pages | | [Frames, tones, lines, and symbols](https://docs.plotwork.dev/cloud-architecture/lines-and-symbols.md) | Frame styles, tones, depth, and lines drawn one by one | | [Icon names and lookup](https://docs.plotwork.dev/cloud-architecture/icon-names.md) | How `provider:product` resolves | | [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md) | Group options in the whole language | | [Group types in the language reference](https://docs.plotwork.dev/reference/language/group-types.md) | Every group type with its meaning and the icon each provider draws | --- # Frames, tones, lines, and symbols > Page: https://docs.plotwork.dev/cloud-architecture/lines-and-symbols. Index of every page: https://docs.plotwork.dev/llms.txt Every frame style, tone, edge style, arrowhead, label, and mark the language draws, each shown as the renderer draws it, with the exact source that makes it. It is for anyone who wants to know what a line or a color means before using it, human or AI agent. ## TL;DR | Question | Answer | |---|---| | What does a dashed frame mean? | A boundary: what is inside is isolated or checked differently. Solid is a container, and dotted is a side of a line. | | Which tones are there? | Eight: `neutral`, `blue`, `violet`, `aqua`, `orange`, `green`, `amber`, `red`. A type sets one, and `tone=` overrides it. | | Can I write a color? | No. Colors are not accepted. The theme maps each tone to light and dark colors that keep text readable. | | Which arrows are there? | Six: `->`, `-->`, `..>`, `--`, `<->`, `<-->`. Solid is a call, dashed is a reply or a looser link, and dotted is an event. | | How do I label a line? | `a -> b : "label"`. The labels `yes` and `no` get a status icon on their own. | | What can a node or a group carry? | `sub=` for a second line, `stack=` for up to three technology marks, `icon=` to change the icon, and `icon=none` to remove it. | | How do I show a set that spans two zones? | An `overlay`, a dashed frame drawn around the members. See [Marks, second lines, and overlays](https://docs.plotwork.dev/cloud-architecture/marks-and-overlays.md). | Every picture on this page is drawn by the renderer from the source beside it, so the page cannot disagree with the renderer. Each picture follows the theme of the page, light or dark. ## 1. Frame styles A group draws a frame with a corner tab that holds its icon and its name. The frame style tells a reader what kind of boundary it is. A group type sets the style, and no option changes it, so pick the type that says what the group is. | Picture | Frame | Types | Reads as | |---|---|---|---| | | Dashed | `region`, `zone`, `network`, `identity-boundary`, `trust-boundary` | A boundary: what is inside is isolated or checked differently | | | Solid | `cloud`, `account`, `subnet`, `public-subnet`, `private-subnet`, `management-subnet`, `isolated-subnet`, `resource-group`, `cluster`, `scale-group`, `service-plan`, `web-tier`, `app-tier`, `data-tier`, `storage-account`, `messaging`, `edge`, `security`, `observability`, `ci-cd`, `environment` | A logical container: things that belong together | | | Dotted | `cloud-boundary`, `on-premises`, `client-side`, `external-systems` | A side of a line: outside the system you are drawing, or the cloud side of a cloud boundary | ## 2. Tones A tone is a color family. Each tone has a fill and an outline for a group, a chip color for the corner tab, and an ink color for text and icons, tuned for light and for dark. The picture shows an untyped group of that tone. The last column lists the group types that take the tone by default. Every picture is drawn from this source, with the tone replaced: ```plotwork flow "Blue tone" direction=down group g "Blue" tone=blue { node n step "Resource" } ``` | Picture | Tone | The default of | |---|---|---| | | `tone=neutral` | `cloud`, `account`, `region`, `zone`, `subnet`, `resource-group`, `trust-boundary`, `on-premises`, `client-side`, `external-systems` | | | `tone=blue` | `network`, `web-tier`, `edge`, `cloud-boundary` | | | `tone=violet` | `private-subnet`, `cluster`, `service-plan`, `app-tier`, `identity-boundary`, `ci-cd` | | | `tone=aqua` | `scale-group`, `data-tier`, `observability` | | | `tone=orange` | `messaging` | | | `tone=green` | `public-subnet` | | | `tone=amber` | `management-subnet`, `storage-account`, `environment` | | | `tone=red` | `isolated-subnet`, `security` | ## 3. Nesting depth A group nested in another is shaded one step stronger than the one around it, in light and in dark, up to four levels. An account, a region, a network, and a subnet read as four levels, and a neutral group deepens toward the ink color in the same way. Depth is automatic; there is nothing to write. **Diagram source** ```diagram flow "Four nested levels" desc="An account holds a region, which holds a network, which holds a subnet with one service." direction=down group acct "Account" type=account { group reg "eu-west-1" type=region { group net "Application VPC" type=network { group sub "Private subnet" type=private-subnet { node app service "Orders API" } } } } ``` *Picture: Four nested levels: 1 node, 0 connections. An account holds a region, which holds a network, which holds a subnet with one service.* **Text description** **Nodes:** - Account: Account - Region: eu-west-1 - Virtual network: Application VPC - Private subnet: Private subnet - Orders API, service **Connections:** ## 4. Edge styles and arrowheads An edge joins two nodes. The token sets the line style and where the arrowheads go. Use `->` for a call that waits, `-->` for a response or a looser dependency, and `..>` for a call that is not waited for, such as a webhook, an event, a pull, or a push. Every picture is drawn from this source, with the last line replaced by the statement in the table: ```plotwork flow "Edge" direction=right node a step "A" node b step "B" a -> b ``` | Picture | Source | Line | Arrowheads | Use it for | |---|---|---|---|---| | | `a -> b` | solid | at the end | A call or a flow of data. | | | `a --> b` | dashed | at the end | A reply, a read, or a link that is optional or indirect. | | | `a ..> b` | dotted | at the end | An asynchronous call: a webhook, an event, a pull, or a push that is not waited for. | | | `a -- b` | solid | none | An association with no direction. | | | `a <-> b` | solid | at both ends | A two-way call or sync. | | | `a <--> b` | dashed | at both ends | A two-way link that is optional or indirect. | ### Labels and icons on an edge | Picture | Source | What it does | |---|---|---| | | `a -> b : "HTTPS"` | A label sits on the line. Keep it to a few words. | | | `a ..> b : "event"` | A dotted line with a label is an event or a webhook. | | | `a -> b : "yes"` | The labels `yes`, `no`, `true`, and `false` get a status icon without being asked: here `status:yes`, a green tick. | | | `a -> b : "no"` | The label `no` gets `status:no`, a red cross. | | | `a -> b : "retry" icon=status:retry` | An explicit `icon=` on the edge wins. It needs a label. | | | `a -> b : "yes" icon=none` | `icon=none` removes the automatic icon. | An edge between two nodes that sit side by side, such as a primary and a replica database, is drawn straight across, so the two columns stay level. Give it `..>` and a label such as `"replication"`. Keep to one such pair in a diagram. **Diagram source** ```diagram flow "Level edge between zones" desc="A primary database in one zone replicates to a replica in the other, on a straight line." direction=right group a "Zone a" type=zone { node primary database "Primary" } group b "Zone b" type=zone { node replica database "Replica" } primary ..> replica : "replication" ``` *Picture: Level edge between zones: 2 nodes, 1 connection. A primary database in one zone replicates to a replica in the other, on a straight line.* **Text description** **Nodes:** - Availability zone: Zone a - Primary, database - Availability zone: Zone b - Replica, database **Connections:** - Primary to Replica (dotted): replication ## 5. Marks and second lines A node card and a group tab hold more than a name. These options add to them, and the card or tab grows to hold them without ever covering the icon or the text. ### On a node | Picture | Source | What it does | |---|---|---| | | `node a service "Orders API"` | A node has a type, which sets its tone and icon, and a label. | | | `node a service "Orders API" sub="Validates credentials"` | `sub=` adds a second line under the label. | | | `node a storage "Web app" icon=azure:blob-storage stack=simple:angular,simple:typescript` | `stack=` adds up to three small technology marks under the text. The card grows to hold them. | | | `node a external "Partner API"` | The type `external` has a dashed outline: a system you call but do not run. | | | `node a service "Orders API" tone=violet` | `tone=` overrides the tone the type sets. | | | `node a database "orders-db" icon=aws:rds change=update` | `change=` marks a planned change with a corner badge and a coloured outline: `create`, `update`, `replace`, or `destroy`. | | | `node a service "Orders API" icon=none` | `icon=none` removes the icon. | ### On a group Every picture is drawn from this source, with the first group line replaced by the statement in the table: ```plotwork flow "Group" direction=down group g "Web tier" type=web-tier { node n step "Resource" } ``` | Picture | Source | What it does | |---|---|---| | | `group g "Web tier" type=web-tier {` | The type sets the icon in the tab, the tone, and the frame style. | | | `group g "Web tier" type=web-tier sub="10.0.1.0/24" {` | `sub=` adds a second line to the tab, such as an address range or a plan. | | | `group g "Plan" type=service-plan stack=simple:dotnet {` | `stack=` adds up to three small marks to the tab, written like the marks of a node. | | | `group g "Network" type=network provider=azure {` | `provider=` makes the type draw that provider's own icon where it has one. | | | `group g "Web tier" type=web-tier icon=lucide:rocket {` | `icon=` replaces the icon in the tab. | | | `group g "Web tier" type=web-tier icon=none {` | `icon=none` removes the icon. | | | `group g "Web tier" type=web-tier tone=amber {` | `tone=` overrides the tone the type sets. Prefer the type that says what the group is. | ### An overlay An overlay is a dashed frame drawn around nodes or groups that it does not own, wherever layout put them. It is one line after the members. Here two machines in two zones form one scaling group, and the frame crosses the zone frames on purpose. **Diagram source** ```diagram flow "Scaling group across two zones" desc="Two web machines, one in each zone, are framed together as one scaling group." direction=right group a "Zone a" type=zone { node w1 service "Web 1" } group b "Zone b" type=zone { node w2 service "Web 2" } overlay set "Web machines" type=scale-group sub="At least 2" members=w1,w2 ``` *Picture: Scaling group across two zones: 2 nodes, 0 connections. Two web machines, one in each zone, are framed together as one scaling group.* **Text description** **Nodes:** - Availability zone: Zone a - Web 1, service - Availability zone: Zone b - Web 2, service **Overlays:** - Scale group overlay: Web machines (At least 2), around Web 1, Web 2 **Connections:** ## See also | Doc | Why | |---|---| | [Group types](https://docs.plotwork.dev/cloud-architecture/group-types.md) | The 30 types of group, each drawn | | [Marks, second lines, and overlays](https://docs.plotwork.dev/cloud-architecture/marks-and-overlays.md) | The rules for `stack=`, `sub=`, and `overlay`, with the warning each can raise | | [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md) | The whole language, kind by kind | | [Language reference](https://docs.plotwork.dev/reference/language.md) | Every arrow, tone, option, and diagnostic code | | [Cloud architecture](https://docs.plotwork.dev/cloud-architecture.md) | The recipe for a cloud diagram | --- # Icon names and lookup > Page: https://docs.plotwork.dev/cloud-architecture/icon-names. Index of every page: https://docs.plotwork.dev/llms.txt How a cloud product becomes an `icon=` name, how search finds it, and how to map the product names in a request to icons with the tools. It is for an AI agent first; a person can use the icon pages in the same way. ## TL;DR | Question | Answer | |---|---| | What is the shape of a name? | `<provider>:<product>`, in lowercase words joined with hyphens, without the provider's name repeated: `aws:lambda`, `azure:cosmos-db`, `gcp:cloud-run`. | | How do I get the icon for a group? | The provider itself is `<provider>:cloud`. Set `type=` and `provider=` on the group and it draws the right one. | | How do I find the name of one product? | Call `search_icons` with the product name as the request writes it, and use the first result. | | How do I find the names of many products? | Call `map_icons` once with up to 40 names and the provider. Each name comes back resolved, with a confidence, or with the candidates to choose from. | | What if nothing matches? | The name is listed in `gaps` with a neutral fallback. Draw the service with its node type and put the product name in the label. | | Where can I see the icons? | On the icon pages: [AWS](https://docs.plotwork.dev/cloud-icons/aws.md), [Azure](https://docs.plotwork.dev/cloud-icons/azure.md), [Google Cloud](https://docs.plotwork.dev/cloud-icons/google-cloud.md), [other providers](https://docs.plotwork.dev/cloud-icons/other-providers.md), and [generic and technology icons](https://docs.plotwork.dev/cloud-icons/generic.md). | | Can I guess a name? | No. An unknown name is an error with the nearest valid names, so validation catches a guess, but a search is faster than a retry. A few everyday words are aliases that draw anyway: `lucide:cart` draws `lucide:shopping-cart` and comes back as note I122 with the real name. | ## 1. How a name resolves An icon reference is a set, a colon, and a name. The set says where the artwork comes from, and the name says which one. The provider sets hold the products of one cloud each. | Set | Holds | Example | |---|---|---| | `aws`, `azure`, `gcp` | The provider's products, one page of icons each | `aws:s3`, `azure:key-vault`, `gcp:bigquery` | | `alibaba`, `oci`, `ibm`, `cloudflare`, `digitalocean`, `vercel` | Other providers, with fewer products, on [one page](https://docs.plotwork.dev/cloud-icons/other-providers.md) | `alibaba:oss`, `cloudflare:workers` | | `simple` | Technology and brand logos | `simple:postgresql`, `simple:auth0`, `simple:angular` | | `lucide` | General icons | `lucide:git-branch` | | `status` | Colored outcome icons | `status:success` | The names come from the product's own name, so they are stable. A general icon is often guessed by an everyday word instead of its Lucide name. A hundred such words are aliases of a bundled icon: the diagram draws the icon, and validation returns note I122 in `notices`, which is information and never an error. Write the real name to clear it. A name that is neither bundled nor an alias is still error E122. | Written | Draws | |---|---| | `lucide:cart`, `lucide:basket`, `lucide:checkout` | `lucide:shopping-cart` | | `lucide:bank` | `lucide:landmark` | | `lucide:support`, `lucide:helpdesk`, `lucide:customer-support` | `lucide:headset` | | `lucide:edit`, `lucide:pen` | `lucide:pencil` | | `lucide:delete`, `lucide:trash-2` | `lucide:trash` | | `lucide:home` | `lucide:house` | | `lucide:money`, `lucide:cash` | `lucide:banknote` | `search_icons` and `map_icons` find each icon by the same words, so a search for `cart` or `bank` returns the icon first. ## 2. How search finds a product `search_icons` understands how people name products. Relevance, best first: | Rank | A query that is | Example | |---|---|---| | 1 | Exactly a product's name, title, or alias, with or without its provider | `lambda`, `aws lambda`, `s3 bucket`, `azure functions` | | 2 | A provider followed by a kind of service | `azure object storage` | | 3 | Two or more words inside a title or alias | `blob storage` | | 4 | A phrase for a kind of service | `message queue` finds every provider's queue | | 5 | Words that match a name, a concept phrase, or a tag | `token exchange`, `defect` | A phrase for a kind of service wins over a product that happens to be named that, so `load balancer` lists every provider's load balancer, most used provider first. Some examples of what comes back: | You search | You get | Why | |---|---|---| | `lambda`, `aws lambda`, `AWS Lambda` | `aws:lambda` | A product name, with or without the provider | | `cosmos`, `cosmos db`, `cosmosdb` | `azure:cosmos-db` | An alias or abbreviation | | `gke`, `google kubernetes engine` | `gcp:gke` | An abbreviation and the full title | | `serverless function` | The function product of each provider, most used provider first | A kind of service | | `azure message queue` | `azure:service-bus` | A provider followed by a kind of service | ## 3. Map the product names of a request A request such as "a web app with a Postgres database, a queue for slow work, and a CDN, on AWS" names several products. Map them all at once. 1. **List the names.** Take each service as the request writes it: `Postgres`, `queue`, `CDN`, `web app`. Do not translate them first. 2. **Call `map_icons`.** Send the names and set `provider` to the cloud the architecture runs on, so that a generic name such as `queue` finds that cloud's product (`aws:sqs`) first. 3. **Read the confidence of each result.** Write the icon of a result whose `resolved` is true. For `ambiguous`, choose from `candidates`, or ask the user, because a kind of service several clouds offer, or a match by idea only, is not a decision for the tool. 4. **Handle the gaps.** A name in `gaps` matched nothing. Use the fallback, or leave `icon=` off so the node type draws its own, and put the name in the label. 5. **Validate.** Call `validate_diagram` or `draw_diagram`. A node whose label names a product but has no icon gets warning `W204` with the exact text to add. | Confidence | Meaning | What to do | |---|---|---| | `exact` | The name is the icon or the product name | Use `icon` | | `strong` | The name clearly points at this icon and no other provider competes | Use `icon` | | `ambiguous` | Several icons fit, or the match is by idea only | Choose from `candidates`, or ask | For one name, `search_icons` returns the matches best first, and the first is the answer. `map_icons` replaces up to 40 of those calls. ## 4. Read an icon page Each icon page lists every icon of a set, grouped by the provider's own categories, with the same layout throughout. | Part | What it shows | |---|---| | Category heading | The provider's category, such as Compute or Databases | | Picture | The icons of the category as the renderer draws them, each above its product name | | Table | The exact `icon=` for each product, its official name, and other names that find it. On the AWS, Azure, and Google Cloud pages it is folded under the picture. | The pictures are extra: a table row holds everything an agent needs, and the picture lets a person see the icon before using it. ## 5. What an icon draws Every cloud product draws one of three things, and the name, the search, and the validation are the same whichever it is, so a diagram means the same thing everywhere. | Artwork | Where it comes from | |---|---| | The vendor's own icon | The official architecture icon of AWS, Azure, or Google Cloud, drawn as the vendor published it, in its original colors, on a light tile in both themes, next to the product name | | A brand mark | A licensed mark, such as the mark of Cloudflare, drawn in the color of the node or group that holds it | | The glyph of a kind of service | A function, a queue, or object storage looks the same on every cloud, and the label says whose it is | See [Trademarks and icons](https://docs.plotwork.dev/trademarks-and-icons.md) for the terms the artwork is used under. ## See also | Doc | Why | |---|---| | [Cloud architecture](https://docs.plotwork.dev/cloud-architecture.md) | The five-step recipe | | [AWS icons](https://docs.plotwork.dev/cloud-icons/aws.md) | Every AWS icon, drawn | | [Azure icons](https://docs.plotwork.dev/cloud-icons/azure.md) | Every Azure icon, drawn | | [Google Cloud icons](https://docs.plotwork.dev/cloud-icons/google-cloud.md) | Every Google Cloud icon, drawn | | [Trademarks and icons](https://docs.plotwork.dev/trademarks-and-icons.md) | The terms for the vendor artwork and the marks | | [Icon reference](https://docs.plotwork.dev/reference/icons.md) | The compact catalog of every bundled icon | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | `map_icons`, `search_icons`, and `draw_diagram` | --- # Marks, second lines, and overlays > Page: https://docs.plotwork.dev/cloud-architecture/marks-and-overlays. Index of every page: https://docs.plotwork.dev/llms.txt How to put technology marks on a service, a second line and marks on a group tab, an overlay around a set that spans zones, and a dotted line for an event. It is for anyone drawing a cloud architecture, human or AI agent. Frames, tones, and every line style are drawn on [Frames, tones, lines, and symbols](https://docs.plotwork.dev/cloud-architecture/lines-and-symbols.md). ## TL;DR | Question | Answer | |---|---| | How do I mark a technology on a service? | `stack=simple:angular,simple:typescript` on the node, up to three marks. The card grows to hold them. | | Which set do the marks come from? | Usually `simple`, the technology and brand logos. Find the name with `search_icons`. | | What if the technology has no mark? | Write it in `sub=` as text. The `simple` set has a mark for .NET (`simple:dotnet`) but none for C#, so write a .NET service as `simple:dotnet`. | | How do I show an event or a webhook? | A dotted arrow: `registry ..> api : "webhook"`. | | How do I add a second line to a group? | `sub="10.0.1.0/24"` on the group. | | How do I put marks on a group? | `stack=simple:dotnet` on the group, written like the marks of a node. | | How do I show a scaling group across two zones? | One `overlay` line after the nodes, listing its members. | | What warns me about an overlay? | `W206`, when the frame would also surround a node that is not a member. | ## 1. Technology marks `stack=` puts small marks on a card for the technologies a service is built with. It takes up to three icon references, usually from the `simple` set, and the card grows to hold them. A mark never covers the text or the icon. ```plotwork node app storage "Web app" icon=azure:blob-storage stack=simple:angular,simple:typescript node api service "Orders API" icon=azure:app-service stack=simple:dotnet ``` Use a mark for the technology of the code, not for the cloud product, which already has its icon. **Diagram source** ```diagram flow "Marks on two services" desc="A web app with Angular and TypeScript marks calls an API with a .NET mark." direction=right node app storage "Web app" icon=azure:blob-storage stack=simple:angular,simple:typescript node api service "Orders API" icon=azure:app-service stack=simple:dotnet app -> api : "HTTPS" ``` *Picture: Marks on two services: 2 nodes, 1 connection. A web app with Angular and TypeScript marks calls an API with a .NET mark.* **Text description** **Nodes:** - Web app, storage, stack: angular, typescript - Orders API, service, stack: dotnet **Connections:** - Web app to Orders API: HTTPS ## 2. Event edges A dotted arrow, `..>`, is an asynchronous call: a webhook, an event, a pull, or a push that is not waited for. Use `->` for a call that waits, and `-->` for a response or a looser dependency. ```plotwork registry ..> api : "webhook, Docker pull" ``` ## 3. Second lines, marks, and overlays on a group A group tab holds more than a name. Three options add to it, and the tab grows to hold them without ever covering the icon or the text. | Option | What it adds | Example | |---|---|---| | `sub="..."` | A second line under the name, such as an address range or a plan | `group web "Web tier" type=web-tier sub="10.0.1.0/24" {` | | `stack=a,b` | Up to three small marks under that, written like the marks of a node | `group plan "Plan" type=service-plan stack=simple:dotnet {` | | `overlay` | A dashed frame around nodes or groups that the overlay does not own | `overlay set "Web machines" type=scale-group members=w1,w2,w3` | An overlay is the answer to "these machines are one scaling group, but they sit in two zones". Write the nodes in their own groups, then write one `overlay` line after them that lists the members. When all the members share one group, the overlay is laid out as a frame of its own. When they sit in different groups, the frame is drawn around them after layout and crosses the group frames on purpose. If the frame would also surround a node that is not a member, `validate_diagram` returns warning `W206` with the three fixes. **Diagram source** ```diagram flow "Overlay across two zones" desc="Two web machines in two zones are framed as one scaling group." direction=right group a "Zone a" type=zone { node w1 service "Web 1" } group b "Zone b" type=zone { node w2 service "Web 2" } overlay set "Web machines" type=scale-group sub="At least 2" members=w1,w2 ``` *Picture: Overlay across two zones: 2 nodes, 0 connections. Two web machines in two zones are framed as one scaling group.* **Text description** **Nodes:** - Availability zone: Zone a - Web 1, service - Availability zone: Zone b - Web 2, service **Overlays:** - Scale group overlay: Web machines (At least 2), around Web 1, Web 2 **Connections:** An edge between two nodes that sit side by side, such as a primary and a replica database, is drawn straight across, so the two columns stay level. Give it `..>` and a label such as `"replication"`. Keep to one such pair in a diagram. ## See also | Doc | Why | |---|---| | [Frames, tones, lines, and symbols](https://docs.plotwork.dev/cloud-architecture/lines-and-symbols.md) | Every mark and option drawn beside its source | | [Generic and technology icons](https://docs.plotwork.dev/cloud-icons/generic.md) | Every `simple` mark, drawn | | [Examples](https://docs.plotwork.dev/cloud-architecture/examples.md) | Marks, overlays, and event edges in complete diagrams | | [Cloud architecture](https://docs.plotwork.dev/cloud-architecture.md) | The five-step recipe | | [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md) | Node and group options in the whole language | --- # Cloud architecture examples > Page: https://docs.plotwork.dev/cloud-architecture/examples. Index of every page: https://docs.plotwork.dev/llms.txt Three complete cloud diagrams, one for each of Azure, AWS, and Google Cloud, each drawn from a checked pattern. It is for anyone who wants to read a finished diagram before writing one, human or AI agent. ## TL;DR | Question | Answer | |---|---| | Where do these come from? | Each example is a pattern from the [architecture patterns guide](https://docs.plotwork.dev/architecture-patterns.md). It validates with no warnings, passes the text-fit check, and renders in light and dark at desktop and phone width. | | Can I copy one? | Yes. The source is in the block above each picture. Every label and value is a placeholder, so rename them to fit the request. | | Where do I start from a pattern? | Call `find_pattern` with the request in plain words, then `draw_from_pattern` with the values to change. | | Which features do they show? | Typed groups, a provider icon on every service, stack marks, overlays, event edges, and a level edge between a primary and a replica. | ## 1. Web application on Azure A single-page app served from storage behind a CDN, services on an app plan, a database, secrets, a queue, sign-in, monitoring, and a delivery pipeline. It uses typed groups, a provider icon on every service, stack marks, and dotted edges for events. **Diagram source** ```diagram flow "Web application on a cloud" provider=azure direction=down desc="Visitors load a single-page app from storage through a content delivery network and sign in through an identity service. The app calls an API on an application plan, which stores uploads, reads a database, and hands slow work to a queue that a background worker drains. A pipeline builds and deploys the API, and a monitoring service collects telemetry." node visitors client "Visitors" sub="Browsers" group edge "Edge" type=edge { node cdn gateway "Content delivery" icon=azure:cdn } group sign-in "Sign-in" type=identity-boundary { node idp identity "Identity service" icon=azure:entra-id } group files "Storage" type=storage-account { node site storage "Web app files" icon=azure:blob-storage stack=simple:react,simple:typescript node uploads storage "Uploads" icon=azure:blob-storage } group plan "Application plan" type=service-plan { node api service "Orders API" icon=azure:app-service stack=simple:nodedotjs } group messaging "Messaging" type=messaging { node jobs queue "Job queue" icon=azure:service-bus } node worker function "Background worker" icon=azure:functions stack=simple:nodedotjs node db database "Database" icon=azure:sql-database group delivery "Delivery pipeline" type=ci-cd { node repo step "Source code" icon=lucide:git-branch node build step "Build pipeline" icon=azure:devops } group monitoring "Monitoring" type=observability { node telemetry service "Metrics and logs" icon=azure:monitor } visitors -> cdn : "HTTPS" visitors -> idp : "sign in" role=identity cdn -> site cdn -> api : "API calls" api -> uploads : "store files" role=data api -> jobs : "enqueue" role=async jobs ..> worker : "event" worker -> db : "writes" role=data api -> db : "reads" role=data repo -> build build ..> api : "deploys image" api ..> telemetry : "telemetry" ``` *Picture: Web application on a cloud: 12 nodes, 12 connections. Visitors load a single-page app from storage through a content delivery network and sign in through an identity service. The app calls an API on an application plan, which stores uploads, reads a database, and hands slow work to a queue that a background worker drains. A pipeline builds and deploys the API, and a monitoring service collects telemetry.* **Text description** **Nodes:** - Visitors (Browsers), client - Background worker, function, stack: nodedotjs - Database, database - Edge: Edge - Content delivery, gateway - Identity: Sign-in - Identity service, identity - Storage: Storage - Web app files, storage, stack: react, typescript - Uploads, storage - Service plan: Application plan - Orders API, service, stack: nodedotjs - Messaging: Messaging - Job queue, queue - CI and CD: Delivery pipeline - Source code, step - Build pipeline, step - Observability: Monitoring - Metrics and logs, service **Connections:** - Visitors to Content delivery: HTTPS - Visitors to Identity service (identity line): sign in - Content delivery to Web app files - Content delivery to Orders API: API calls - Orders API to Uploads (stored data line): store files - Orders API to Job queue (asynchronous line): enqueue - Job queue to Background worker (dotted): event - Background worker to Database (stored data line): writes - Orders API to Database (stored data line): reads - Source code to Build pipeline - Build pipeline to Orders API (dotted): deploys image - Orders API to Metrics and logs (dotted): telemetry ## 2. Two-zone network on AWS A network in a region with two zones. Each zone has a public, a private, and an isolated subnet. Load balancers and a scaling group span both zones with overlays, and the primary database replicates to its replica across the zones. **Diagram source** ```diagram flow "Two-zone network with a replicated database" provider=aws direction=down desc="Traffic enters through an internet gateway and reaches load balancers in the public subnets of two availability zones. Each balancer sends it to an application server in a private subnet, and the two servers form one scaling group. Each server writes to a database in an isolated subnet, and the primary database replicates to the replica in the other zone." node internet external "Internet" group cloud "Cloud" type=cloud { node igw gateway "Internet gateway" icon=aws:internet-gateway group vpc "Application network" type=network sub="10.0.0.0/16" { group za "Zone A" type=zone { group pub-a "Public subnet A" type=public-subnet sub="10.0.0.0/24" { node lb-a gateway "Load balancer A" icon=aws:elb } group app-a "Private subnet A" type=private-subnet sub="10.0.1.0/24" { node web-a service "Application server A" icon=aws:ec2 } group db-za "Isolated subnet A" type=isolated-subnet sub="10.0.2.0/24" { node db-a database "Primary database" icon=aws:rds } } group zb "Zone B" type=zone { group pub-b "Public subnet B" type=public-subnet sub="10.0.10.0/24" { node lb-b gateway "Load balancer B" icon=aws:elb } group app-b "Private subnet B" type=private-subnet sub="10.0.11.0/24" { node web-b service "Application server B" icon=aws:ec2 } group db-zb "Isolated subnet B" type=isolated-subnet sub="10.0.12.0/24" { node db-b database "Replica database" icon=aws:rds } } } } overlay balancer "Load balancer" type=edge members=lb-a,lb-b overlay scaling "Scaling group" type=scale-group members=web-a,web-b internet <-> igw igw -> lb-a igw -> lb-b lb-a -> web-a lb-b -> web-b web-a -> db-a web-b -> db-b db-a ..> db-b : "replication" ``` *Picture: Two-zone network with a replicated database: 8 nodes, 8 connections. Traffic enters through an internet gateway and reaches load balancers in the public subnets of two availability zones. Each balancer sends it to an application server in a private subnet, and the two servers form one scaling group. Each server writes to a database in an isolated subnet, and the primary database replicates to the replica in the other zone.* **Text description** **Nodes:** - Internet, external - Cloud: Cloud - Internet gateway, gateway - Virtual network: Application network (10.0.0.0/16) - Availability zone: Zone A - Public subnet: Public subnet A (10.0.0.0/24) - Load balancer A, gateway - Private subnet: Private subnet A (10.0.1.0/24) - Application server A, service - Isolated subnet: Isolated subnet A (10.0.2.0/24) - Primary database, database - Availability zone: Zone B - Public subnet: Public subnet B (10.0.10.0/24) - Load balancer B, gateway - Private subnet: Private subnet B (10.0.11.0/24) - Application server B, service - Isolated subnet: Isolated subnet B (10.0.12.0/24) - Replica database, database **Overlays:** - Edge overlay: Load balancer, around Load balancer A, Load balancer B - Scale group overlay: Scaling group, around Application server A, Application server B **Connections:** - Internet with Internet gateway - Internet gateway to Load balancer A - Internet gateway to Load balancer B - Load balancer A to Application server A - Load balancer B to Application server B - Application server A to Primary database - Application server B to Replica database - Primary database to Replica database (dotted): replication ## 3. Serverless API on Google Cloud A function behind an API gateway with a database, a queue for slow work, and storage for files, drawn with Google Cloud icons. **Diagram source** ```diagram flow "Serverless API" provider=gcp direction=down desc="A client calls an API gateway, which checks the caller with a sign-in service and routes each request to one of three functions. The functions keep records in a managed database and files in object storage, and send logs and metrics to monitoring." node client client "Client app" sub="Web or mobile" node gateway gateway "Orders API" sub="API Gateway" icon=gcp:api-gateway group fns "Functions" type=app-tier icon=gcp:cloud-functions { node create function "Create order" sub="Cloud Functions" icon=gcp:cloud-functions node list function "List orders" sub="Cloud Functions" icon=gcp:cloud-functions node get function "Get order" sub="Cloud Functions" icon=gcp:cloud-functions } group signin "Sign-in" type=identity-boundary { node auth identity "User directory" sub="Identity Platform" icon=gcp:identity-platform } group data "Data stores" type=data-tier icon=gcp:firestore { node bucket storage "Order files" sub="Cloud Storage" icon=gcp:cloud-storage node db database "Orders database" sub="Firestore" icon=gcp:firestore } group obs "Monitoring" type=observability { node monitor service "Logs and metrics" sub="Cloud Monitoring" icon=gcp:cloud-monitoring } client -> gateway : "HTTPS" gateway -> create : "POST /orders" gateway -> list : "GET /orders" gateway -> get : "GET /orders/id" gateway -> auth : "verify token" role=identity create -> bucket : "upload" role=data create -> db : "write" role=data list -> db get -> db get ..> monitor : "logs and metrics" ``` *Picture: Serverless API: 9 nodes, 10 connections. A client calls an API gateway, which checks the caller with a sign-in service and routes each request to one of three functions. The functions keep records in a managed database and files in object storage, and send logs and metrics to monitoring.* **Text description** **Nodes:** - Client app (Web or mobile), client - Orders API (API Gateway), gateway - Application tier: Functions - Create order (Cloud Functions), function - List orders (Cloud Functions), function - Get order (Cloud Functions), function - Identity: Sign-in - User directory (Identity Platform), identity - Data tier: Data stores - Order files (Cloud Storage), storage - Orders database (Firestore), database - Observability: Monitoring - Logs and metrics (Cloud Monitoring), service **Connections:** - Client app to Orders API: HTTPS - Orders API to Create order: POST /orders - Orders API to List orders: GET /orders - Orders API to Get order: GET /orders/id - Orders API to User directory (identity line): verify token - Create order to Order files (stored data line): upload - Create order to Orders database (stored data line): write - List orders to Orders database - Get order to Orders database - Get order to Logs and metrics (dotted): logs and metrics ## See also | Doc | Why | |---|---| | [Architecture patterns](https://docs.plotwork.dev/architecture-patterns.md) | Every pattern, with its slots and the advice on what to keep, rename, and split | | [Cloud architecture](https://docs.plotwork.dev/cloud-architecture.md) | The five-step recipe | | [Group types](https://docs.plotwork.dev/cloud-architecture/group-types.md) | The types the examples use, each drawn | | [Marks, second lines, and overlays](https://docs.plotwork.dev/cloud-architecture/marks-and-overlays.md) | The marks and overlays the examples use | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The validate-and-fix loop | --- # Architecture gallery > Page: https://docs.plotwork.dev/cloud-architecture/gallery. Index of every page: https://docs.plotwork.dev/llms.txt Five full-size cloud and network architectures drawn in Plotwork, with the text that makes each one and the prompt that gets an agent to write something like it. Read them to see what the language does with real systems, then copy the text and change the names. It is for anyone drawing an environment with accounts, networks, tunnels, and several sites. ## TL;DR | Question | Answer | |---|---| | What is here? | A disaster recovery setup that spans AWS and a managed host, a multi-account AWS network with numbered traffic flows, an edge network where tunnels reach private origins, an Azure hub and spoke with private DNS and a private database, and the same Azure workload shown by tenant, management group, subscription, and resource group. | | What do they show that a small diagram does not? | Groups nested four deep, typed groups with their icons, provider icons on every service, [numbered steps](https://docs.plotwork.dev/writing-diagrams.md#7-numbered-steps) that say what happens in what order, and [animated lines](https://docs.plotwork.dev/writing-diagrams.md#8-animated-lines) for traffic that moves. | | How do I use one? | Paste the prompt into your agent and change the names, or copy the text into a `diagram` fence. The text is the whole diagram. | | Why is the text short? | The engine lays out the groups, routes the lines, sizes the boxes to fit the words, and draws both color schemes. You write what is in what and what talks to what. | | Do the lines move on GitHub? | Animated lines move when the SVG is shown as an image in a browser, including in a README on GitHub. A PNG export, an email, or a reader that flattens SVG shows them still, and motion stops for a reader who asks for reduced motion. | ## 1. Hybrid disaster recovery A production site and a disaster recovery site, each with a managed host and an AWS account behind it. Staff and customers connect over site-to-site VPN, and the two sites replicate over a private WAN. What to notice: - Groups nest four deep (site, host, power environment, and AWS account) and each has a `type=` that picks its icon and tone. - The two sites sit side by side because both are reached from the customer gateways. Nothing in the text says where to put them. - Only the lines that carry traffic move (`animate=on`): the VPN tunnels and the replication. The rest stays still, so the eye finds the data in flight. Ask your agent: ```text Use Plotwork to draw a hybrid disaster recovery design. A customer's users and on-premises resources connect through customer gateways over site-to-site VPN to a production site and a disaster recovery site. Each site has a managed host with a VPN gateway, an isolated power environment running IBM i partitions with flash storage, and WAN routers, plus an AWS account with Direct Connect to application servers and object storage. The two sites replicate over a private multipath WAN. Engineering support centres reach both sites through a remote management gateway over VPN. Animate the VPN and replication lines. ``` **Diagram source** ```diagram flow "Hybrid disaster recovery across AWS and a managed host" desc="A customer reaches a production site and a disaster recovery site over site-to-site VPN. Each site runs IBM i workloads in an isolated managed environment, linked to AWS over Direct Connect, and the two sites replicate over a private WAN." direction=down group customer "Customer" type=on-premises { node users person "Production, remote and development users" node onprem service "On-premises resources" node cgw gateway "Customer gateways" icon=aws:vpc-customer-gateway sub="Site-to-site VPN" } group support "Engineering support centres" type=management-subnet { node engineers person "IBM i, AWS, network, NOC and OS engineers" node mgmt gateway "Remote management gateway" icon=lucide:router node platform service "Management platform" sub="Monitoring, tickets, runbooks" } group east "Production site" type=region { group east-host "Managed host, US East" type=cloud-boundary { node east-vpn gateway "VPN gateway" icon=aws:vpc-vpn-gateway group east-power "Power environment, isolated VRF" type=isolated-subnet { node east-ibm service "IBM i partitions" sub="Production workloads" node east-flash storage "Flash storage" } node east-wan gateway "WAN routers" icon=lucide:router } group east-aws "AWS, US East" type=account provider=aws { node east-dc gateway "Direct Connect" icon=aws:direct-connect sub="Dedicated, under 2 ms" group east-az "Availability zones" type=zone { node east-app service "Application servers" icon=aws:ec2 node east-objects storage "Object storage" icon=aws:s3 } } } group west "Disaster recovery site" type=region { group west-host "Managed host, US West" type=cloud-boundary { node west-vpn gateway "VPN gateway" icon=aws:vpc-vpn-gateway group west-power "Power environment, isolated VRF" type=isolated-subnet { node west-ibm service "IBM i partitions" sub="Standby, replicated" node west-flash storage "Flash storage" } node west-wan gateway "WAN routers" icon=lucide:router } group west-aws "AWS, US West" type=account provider=aws { node west-dc gateway "Direct Connect" icon=aws:direct-connect sub="Dedicated, under 2 ms" group west-az "Availability zones" type=zone { node west-app service "Application servers" icon=aws:ec2 sub="Stopped until failover" node west-objects storage "Object storage" icon=aws:s3 } } } node wan gateway "Private WAN" icon=lucide:network sub="Multipath, between sites" users -> cgw onprem -> cgw cgw -> east-vpn : "VPN" animate=on cgw -> west-vpn : "VPN" animate=on engineers -> mgmt mgmt -> platform mgmt -> east-vpn : "Management VPN" animate=on mgmt -> west-vpn : "Management VPN" animate=on east-vpn -> east-ibm west-vpn -> west-ibm east-ibm -> east-flash west-ibm -> west-flash east-ibm -> east-wan west-ibm -> west-wan east-wan <-> wan : "VTL and SAN replication" animate=on west-wan <-> wan : "VTL and SAN replication" animate=on east-wan -> east-dc west-wan -> west-dc east-dc -> east-app west-dc -> west-app east-app -> east-objects west-app -> west-objects ``` *Picture: Hybrid disaster recovery across AWS and a managed host: 21 nodes, 22 connections. A customer reaches a production site and a disaster recovery site over site-to-site VPN. Each site runs IBM i workloads in an isolated managed environment, linked to AWS over Direct Connect, and the two sites replicate over a private WAN.* **Text description** **Nodes:** - Private WAN (Multipath, between sites), gateway - On premises: Customer - Production, remote and development users, person - On-premises resources, service - Customer gateways (Site-to-site VPN), gateway - Management subnet: Engineering support centres - IBM i, AWS, network, NOC and OS engineers, person - Remote management gateway, gateway - Management platform (Monitoring, tickets, runbooks), service - Region: Production site - Cloud boundary: Managed host, US East - VPN gateway, gateway - WAN routers, gateway - Isolated subnet: Power environment, isolated VRF - IBM i partitions (Production workloads), service - Flash storage, storage - Account: AWS, US East - Direct Connect (Dedicated, under 2 ms), gateway - Availability zone: Availability zones - Application servers, service - Object storage, storage - Region: Disaster recovery site - Cloud boundary: Managed host, US West - VPN gateway, gateway - WAN routers, gateway - Isolated subnet: Power environment, isolated VRF - IBM i partitions (Standby, replicated), service - Flash storage, storage - Account: AWS, US West - Direct Connect (Dedicated, under 2 ms), gateway - Availability zone: Availability zones - Application servers (Stopped until failover), service - Object storage, storage **Connections:** - Production, remote and development users to Customer gateways - On-premises resources to Customer gateways - Customer gateways to VPN gateway: VPN - Customer gateways to VPN gateway: VPN - IBM i, AWS, network, NOC and OS engineers to Remote management gateway - Remote management gateway to Management platform - Remote management gateway to VPN gateway: Management VPN - Remote management gateway to VPN gateway: Management VPN - VPN gateway to IBM i partitions - VPN gateway to IBM i partitions - IBM i partitions to Flash storage - IBM i partitions to Flash storage - IBM i partitions to WAN routers - IBM i partitions to WAN routers - WAN routers with Private WAN: VTL and SAN replication - WAN routers with Private WAN: VTL and SAN replication - WAN routers to Direct Connect - WAN routers to Direct Connect - Direct Connect to Application servers - Direct Connect to Application servers - Application servers to Object storage - Application servers to Object storage ## 2. Multi-account AWS network with numbered flows Three AWS accounts joined by a Transit Gateway: networking, shared services, and an application account. The numbers on the lines say what happens, in order, from a visitor's request to the outbound traffic of the application. What to notice: - `numbered=on` in the header puts a numbered badge on every line in the order the lines are written. The text description reads "1. External users to Internet gateway: Browse the site" and so on, so the order survives for someone who cannot see the picture. - `provider=aws` in the header sets the icons of the typed groups, and each service carries its own `icon=aws:` name. - A line can skip a number with `step=off`, or set one with `step=12`, when a flow has side paths. Ask your agent: ```text Use Plotwork to draw a multi-account AWS network with numbered traffic flows. A networking account holds a DMZ VPC with an internet gateway, a Direct Connect connection, a Transit Gateway, and an egress VPC with a NAT gateway. A shared services account holds Managed Microsoft AD and a management host in private subnets. An application account has an Elastic Load Balancing tier in public subnets and an auto scaling group in private subnets. Number the steps: external users reach the site through the internet gateway and the Transit Gateway to the load balancer, then the application authenticates users against the directory. Corporate staff connect over Direct Connect through the Transit Gateway to the management host. The application sends outbound updates through the NAT gateway. Animate the lines that carry user traffic. ``` **Diagram source** ```diagram flow "Multi-account AWS network with numbered traffic flows" desc="Internet users and corporate staff reach applications through a networking account. A Transit Gateway joins the networking, shared services and application accounts, and the numbers show each hop in order." direction=down numbered=on provider=aws node ext person "External users" node corp person "Corporate users" group apps "Application account A" type=account { group apps-vpc "Application VPC" type=network { group apps-public "Public subnets, 3 zones" type=public-subnet { node elb gateway "Elastic Load Balancing" icon=aws:elb } group apps-private "Private subnets, 3 zones" type=private-subnet { node app service "Application instances" icon=aws:ec2 sub="Auto scaling group" } } } group networking "Networking account" type=account { group dmz "DMZ VPC" type=network { node igw gateway "Internet gateway" icon=aws:internet-gateway } node dx gateway "Direct Connect" icon=aws:direct-connect sub="Dedicated connection" node tgw gateway "Transit Gateway" icon=aws:transit-gateway sub="Routes every account" group egress "Networking egress VPC" type=network { node nat gateway "NAT gateway" icon=aws:nat-gateway sub="One per zone" } } group shared "Shared services account" type=account { group shared-vpc "Shared services VPC" type=network { group shared-private "Private subnets, 3 zones" type=private-subnet { node ad service "Managed Microsoft AD" icon=aws:directory-service-managed-microsoft-ad node host service "Management host" icon=aws:ec2 } } } ext -> igw : "Browse the site" animate=on igw -> tgw : "Route to the application account" tgw -> elb : "HTTPS" elb -> app : "Forward the request" animate=on app -> ad : "Authenticate users" role=identity corp -> dx : "Corporate staff connect" dx -> tgw tgw -> host : "Administer" app -> nat : "Outbound updates" animate=on ``` *Picture: Multi-account AWS network with numbered traffic flows: 10 nodes, 9 connections. Internet users and corporate staff reach applications through a networking account. A Transit Gateway joins the networking, shared services and application accounts, and the numbers show each hop in order.* **Text description** **Nodes:** - External users, person - Corporate users, person - Account: Application account A - Virtual network: Application VPC - Public subnet: Public subnets, 3 zones - Elastic Load Balancing, gateway - Private subnet: Private subnets, 3 zones - Application instances (Auto scaling group), service - Account: Networking account - Direct Connect (Dedicated connection), gateway - Transit Gateway (Routes every account), gateway - Virtual network: DMZ VPC - Internet gateway, gateway - Virtual network: Networking egress VPC - NAT gateway (One per zone), gateway - Account: Shared services account - Virtual network: Shared services VPC - Private subnet: Private subnets, 3 zones - Managed Microsoft AD, service - Management host, service **Connections:** - 1. External users to Internet gateway: Browse the site - 2. Internet gateway to Transit Gateway: Route to the application account - 3. Transit Gateway to Elastic Load Balancing: HTTPS - 4. Elastic Load Balancing to Application instances: Forward the request - 5. Application instances to Managed Microsoft AD (identity line): Authenticate users - 6. Corporate users to Direct Connect: Corporate staff connect - 7. Direct Connect to Transit Gateway - 8. Transit Gateway to Management host: Administer - 9. Application instances to NAT gateway: Outbound updates ## 3. An edge network with tunnels to private origins Visitors and remote staff reach the nearest edge server. Every server runs every service, and a small agent next to each origin keeps an outbound-only tunnel open, so nothing behind it has a public address. What to notice: - The services sit in one group, "Every service, on every server", in two chains: application protection on the left and zero trust on the right. Each chain reads top to bottom in the order traffic meets it. - The tunnels are the only lines that move, so the picture shows which traffic crosses the boundary into private space. - The icons come from the `cloudflare:` set, and the nodes without a product icon keep the default glyph of their type. Ask your agent: ```text Use Plotwork to draw an edge network where tunnels reach private origins. Visitors on a public network reach an edge server. Remote staff run a device agent that opens an encrypted tunnel to a second edge server. Inside the edge network a group called "Every service, on every server" holds two chains: DDoS protection, an L3 and L4 firewall, a web application firewall, bot management, load balancing and a CDN; and zero trust network access, a secure web gateway, data loss prevention, a cloud access security broker and remote browser isolation. An IaaS group with an application and a data centre with a private network of two databases each run a cloudflared agent, and a tunnel connects each to the edge network. Animate the tunnels. ``` **Diagram source** ```diagram flow "Cloudflare Anycast network" desc="Visitors and remote staff reach the nearest Cloudflare server. Every server runs every service, and lightweight tunnel agents carry traffic to applications in a cloud and to private networks in a data centre, so no origin has a public address." direction=down group public "Public network" type=client-side { node visitor person "Visitors" sub="Browser and mobile" } group home "Home office" type=client-side { node staff person "Remote staff" node agent service "Device agent" icon=cloudflare:access sub="Zero trust client" } group internet "Internet" type=cloud-boundary { group anycast "Cloudflare Anycast network" type=edge { node edge-a service "Edge server" icon=lucide:server sub="Nearest data centre" node edge-b service "Edge server" icon=lucide:server sub="Nearest data centre" group services "Every service, on every server" type=security { node ddos service "DDoS protection" node fw service "L3 and L4 firewall" node waf service "Web application firewall" icon=cloudflare:waf node bots service "Bot management" node lb service "Load balancing" node cdn service "CDN and cache" icon=cloudflare:cdn node ztna service "Zero trust network access" icon=cloudflare:access node swg service "Secure web gateway" node dlp service "Data loss prevention" node casb service "Cloud access security broker" node rbi service "Remote browser isolation" } } } group iaas "IaaS" type=cloud { node cf-iaas service "cloudflared" icon=cloudflare:cloud sub="Outbound-only tunnel" node app service "Application" } group dc "Data centre" type=on-premises { node cf-dc service "cloudflared" icon=cloudflare:cloud sub="Outbound-only tunnel" group private "Private network" type=private-subnet { node db1 database "Database" node db2 database "Database" } } visitor -> edge-a : "HTTPS" agent -> edge-b : "Encrypted tunnel" animate=on staff -> agent edge-a -> ddos ddos -> fw fw -> waf waf -> bots bots -> lb lb -> cdn edge-b -> ztna ztna -> swg swg -> dlp dlp -> casb casb -> rbi cdn -> cf-iaas : "Tunnel" animate=on cf-iaas -> app rbi -> cf-dc : "Tunnel" animate=on cf-dc -> db1 cf-dc -> db2 ``` *Picture: Cloudflare Anycast network: 21 nodes, 19 connections. Visitors and remote staff reach the nearest Cloudflare server. Every server runs every service, and lightweight tunnel agents carry traffic to applications in a cloud and to private networks in a data centre, so no origin has a public address.* **Text description** **Nodes:** - Client side: Public network - Visitors (Browser and mobile), person - Client side: Home office - Remote staff, person - Device agent (Zero trust client), service - Cloud boundary: Internet - Edge: Cloudflare Anycast network - Edge server (Nearest data centre), service - Edge server (Nearest data centre), service - Security: Every service, on every server - DDoS protection, service - L3 and L4 firewall, service - Web application firewall, service - Bot management, service - Load balancing, service - CDN and cache, service - Zero trust network access, service - Secure web gateway, service - Data loss prevention, service - Cloud access security broker, service - Remote browser isolation, service - Cloud: IaaS - cloudflared (Outbound-only tunnel), service - Application, service - On premises: Data centre - cloudflared (Outbound-only tunnel), service - Private subnet: Private network - Database, database - Database, database **Connections:** - Visitors to Edge server: HTTPS - Device agent to Edge server: Encrypted tunnel - Remote staff to Device agent - Edge server to DDoS protection - DDoS protection to L3 and L4 firewall - L3 and L4 firewall to Web application firewall - Web application firewall to Bot management - Bot management to Load balancing - Load balancing to CDN and cache - Edge server to Zero trust network access - Zero trust network access to Secure web gateway - Secure web gateway to Data loss prevention - Data loss prevention to Cloud access security broker - Cloud access security broker to Remote browser isolation - CDN and cache to cloudflared: Tunnel - cloudflared to Application - Remote browser isolation to cloudflared: Tunnel - cloudflared to Database - cloudflared to Database ## 4. Azure hub and spoke with private DNS and a private database A React frontend on a Web App and an API on a Function App share one App Service plan in a workload spoke. Users arrive through Front Door and Private Link, the API reaches PostgreSQL Flexible Server, Key Vault, and Storage over private addresses, and a hub firewall and DNS resolver serve every spoke. What to notice: - The spoke and the hub are two `type=network` groups with subnets inside them, each with its address range in `sub=`. A subnet delegated to a service (App Service, PostgreSQL) says so in its name. - The App Service plan is a group of its own (`type=service-plan`) around the two apps that run on it. - The numbered steps follow one request from the browser to the database and to the vault. The lines that only keep the platform running (egress through the firewall, DNS lookups, telemetry) use `step=off`, so they are drawn without taking a number. - Four private DNS zones sit together, linked to the hub and the spoke, and the resolver in the hub answers for all of them. Ask your agent: ```text Use Plotwork to draw an Azure hub and spoke network with private DNS. Users reach a Front Door Premium with WAF, which reaches a Web App (React frontend) through a private endpoint. The Web App and a Function App (the API) run on one zone-redundant App Service plan in an integration subnet delegated to App Service, in a workload spoke VNet. The Function App reads secrets from Key Vault and files from a Storage account through private endpoints in a separate subnet, and queries PostgreSQL Flexible Server with private access in a subnet delegated to PostgreSQL. A hub VNet holds an Azure Firewall for egress and a Private DNS resolver, which answers for four private DNS zones: azurewebsites, vaultcore, blob, and postgres. Application Insights and a Log Analytics workspace collect telemetry. Number the steps of one request and animate the Private Link, the API call and the database lines. ``` **Diagram source** ```diagram flow "Azure hub and spoke with private DNS and a private database" desc="A React frontend on a Web App and an API on a Function App share one App Service plan in a workload spoke. Users arrive through Front Door and Private Link, the API reaches PostgreSQL Flexible Server, Key Vault and Storage over private addresses, and a hub firewall and DNS resolver serve every spoke." direction=down numbered=on provider=azure node users client "Users" sub="Browsers" node afd gateway "Front Door Premium" icon=azure:front-door sub="WAF and global entry" group spoke "Workload spoke VNet" type=network sub="10.1.0.0/16" { group entry "Frontend endpoint subnet" type=isolated-subnet sub="10.1.2.0/25" { node pe-web gateway "Private endpoint: Web App" icon=azure:private-endpoints } group integration "Integration subnet, delegated to App Service" type=private-subnet sub="10.1.1.0/24" { group plan "App Service plan" type=service-plan icon=azure:app-service-plans sub="Premium, zone redundant" { node web service "Web App" icon=azure:app-service sub="React frontend" node func function "Function App" icon=azure:functions sub="API" } } group endpoints "Backend endpoint subnet" type=isolated-subnet sub="10.1.2.128/25" { node pe-kv gateway "Private endpoint: Key Vault" icon=azure:private-endpoints node pe-st gateway "Private endpoint: Storage" icon=azure:private-endpoints } group data "Database subnet, delegated to PostgreSQL" type=isolated-subnet sub="10.1.3.0/24" { node pg database "PostgreSQL Flexible Server" icon=azure:database-for-postgresql sub="Private access, zone redundant HA" } } group platform "Platform services, public access disabled" type=data-tier { node kv secret "Key Vault" icon=azure:key-vault sub="Secrets and keys" node st storage "Storage account" icon=azure:storage-accounts sub="Blob files" } group hub "Hub VNet" type=network sub="10.0.0.0/16" { group fw-subnet "AzureFirewallSubnet" type=subnet sub="10.0.0.0/26" { node fw gateway "Azure Firewall" icon=azure:firewall sub="Egress rules and DNS proxy" } group dns-subnet "DNS resolver subnet" type=subnet sub="10.0.1.0/28" { node resolver gateway "Private DNS resolver" icon=azure:dns-private-resolver } } group zones "Private DNS zones, linked to the hub and the spoke" type=data-tier { node dns-web service "privatelink.azurewebsites.net" icon=azure:dns-zones node dns-kv service "privatelink.vaultcore.azure.net" icon=azure:dns-zones node dns-st service "privatelink.blob.core.windows.net" icon=azure:dns-zones node dns-pg service "private.postgres.database.azure.com" icon=azure:dns-zones } group monitoring "Monitoring" type=observability { node insights service "Application Insights" icon=azure:application-insights node logs service "Log Analytics workspace" icon=azure:log-analytics } users -> afd : "HTTPS" afd -> pe-web : "Private Link origin" animate=on pe-web -> web : "Frontend assets" web -> func : "API calls over VNet integration" animate=on func -> pg : "SQL over the private subnet" animate=on func -> pe-kv : "Secrets" pe-kv -> kv func -> pe-st : "Files" pe-st -> st func -> fw : "Egress through the hub" step=off fw -> resolver : "DNS proxy" step=off resolver -> dns-web step=off resolver -> dns-kv step=off resolver -> dns-st step=off resolver -> dns-pg step=off func -> insights step=off insights -> logs step=off ``` *Picture: Azure hub and spoke with private DNS and a private database: 18 nodes, 17 connections. A React frontend on a Web App and an API on a Function App share one App Service plan in a workload spoke. Users arrive through Front Door and Private Link, the API reaches PostgreSQL Flexible Server, Key Vault and Storage over private addresses, and a hub firewall and DNS resolver serve every spoke.* **Text description** **Nodes:** - Users (Browsers), client - Front Door Premium (WAF and global entry), gateway - Virtual network: Workload spoke VNet (10.1.0.0/16) - Isolated subnet: Frontend endpoint subnet (10.1.2.0/25) - Private endpoint: Web App, gateway - Private subnet: Integration subnet, delegated to App Service (10.1.1.0/24) - Service plan: App Service plan (Premium, zone redundant) - Web App (React frontend), service - Function App (API), function - Isolated subnet: Backend endpoint subnet (10.1.2.128/25) - Private endpoint: Key Vault, gateway - Private endpoint: Storage, gateway - Isolated subnet: Database subnet, delegated to PostgreSQL (10.1.3.0/24) - PostgreSQL Flexible Server (Private access, zone redundant HA), database - Data tier: Platform services, public access disabled - Key Vault (Secrets and keys), secret - Storage account (Blob files), storage - Virtual network: Hub VNet (10.0.0.0/16) - Subnet: AzureFirewallSubnet (10.0.0.0/26) - Azure Firewall (Egress rules and DNS proxy), gateway - Subnet: DNS resolver subnet (10.0.1.0/28) - Private DNS resolver, gateway - Data tier: Private DNS zones, linked to the hub and the spoke - privatelink.azurewebsites.net, service - privatelink.vaultcore.azure.net, service - privatelink.blob.core.windows.net, service - private.postgres.database.azure.com, service - Observability: Monitoring - Application Insights, service - Log Analytics workspace, service **Connections:** - 1. Users to Front Door Premium: HTTPS - 2. Front Door Premium to Private endpoint: Web App: Private Link origin - 3. Private endpoint: Web App to Web App: Frontend assets - 4. Web App to Function App: API calls over VNet integration - 5. Function App to PostgreSQL Flexible Server: SQL over the private subnet - 6. Function App to Private endpoint: Key Vault: Secrets - 7. Private endpoint: Key Vault to Key Vault - 8. Function App to Private endpoint: Storage: Files - 9. Private endpoint: Storage to Storage account - Function App to Azure Firewall: Egress through the hub - Azure Firewall to Private DNS resolver: DNS proxy - Private DNS resolver to privatelink.azurewebsites.net - Private DNS resolver to privatelink.vaultcore.azure.net - Private DNS resolver to privatelink.blob.core.windows.net - Private DNS resolver to private.postgres.database.azure.com - Function App to Application Insights - Application Insights to Log Analytics workspace ## 5. Azure landing zone: tenant, subscriptions, and resource groups The Azure workload of the previous example, drawn again with where each part is owned. One Microsoft Entra tenant signs users in with Conditional Access and multifactor authentication (MFA), a tree of management groups applies Azure Policy, and three subscriptions (connectivity, management, and workload) hold the resource groups that contain every resource. What to notice: - The nesting is the ownership chain: tenant root management group, then a management group, then a subscription (`type=account`), then a resource group (`type=resource-group`), then the resource. Each level has its own icon from the Azure set. - The Entra tenant is a group of its own (`type=identity-boundary`) with the sign-in services in it: Entra ID, Conditional Access with MFA, Privileged Identity Management, and app registrations. - The first three numbers are the sign-in: the user signs in, Conditional Access evaluates it, and a token is issued before the request goes on to Front Door. The API then uses a managed identity, with no stored credentials, to read secrets and to connect to PostgreSQL with an Entra token. - Private endpoints sit in the resource group of the thing they protect, next to Key Vault, which is where Azure puts them by default. - Platform lines (egress, DNS, telemetry, policy) use `step=off`, so the numbers stay on the request path. Ask your agent: ```text Use Plotwork to draw an Azure landing zone with the ownership shown. Put a Microsoft Entra tenant at the top with Entra ID, Conditional Access and MFA, Privileged Identity Management, and app registrations. Below it a tenant root management group holds Azure Policy and Defender for Cloud and two child management groups: landing zones, with a workload production subscription, and platform, with a connectivity subscription and a management subscription. In the workload subscription put three resource groups: spoke network (a spoke VNet with a private endpoint subnet for the Web App), app (an App Service plan with a React Web App and a Function App API, plus a managed identity), and data (a private endpoint for Key Vault, Key Vault with RBAC, and PostgreSQL Flexible Server with Entra authentication). In the connectivity subscription put a hub network resource group (Azure Firewall and a Private DNS resolver) and a private DNS resource group (zones for azurewebsites, vaultcore and postgres). In the management subscription put a monitoring resource group with Application Insights and a Log Analytics workspace. Number one request: the user signs in, Conditional Access evaluates it, a token is issued, Front Door reaches the Web App by Private Link, the Web App calls the Function App with the bearer token, the Function App's managed identity reads Key Vault and connects to PostgreSQL. Animate the Private Link, the API call and the database connection. ``` **Diagram source** ```diagram flow "Azure landing zone: tenant, subscriptions and resource groups" desc="The same hub and spoke workload shown with where everything is owned: one Microsoft Entra tenant signs users in with Conditional Access and MFA, a management group tree applies policy, and three subscriptions (connectivity, management and workload) hold the resource groups that contain each resource." direction=down numbered=on provider=azure node users client "Users" sub="Browsers" group tenant "Microsoft Entra tenant" type=identity-boundary icon=azure:entra-id { node entra identity "Entra ID" icon=azure:entra-id sub="Users, groups and sign-in" node mfa service "Conditional Access and MFA" icon=azure:conditional-access sub="Require MFA and a compliant device" node pim service "Privileged Identity Management" icon=lucide:shield-check sub="Just-in-time admin roles" node appreg service "App registrations" icon=azure:app-registrations sub="Frontend and API" } node afd gateway "Front Door Premium" icon=azure:front-door sub="WAF and global entry" group root "Tenant root management group" type=cloud icon=azure:management-groups { node policy service "Azure Policy" icon=azure:policy sub="Allowed regions, required tags, private only" node defender service "Defender for Cloud" icon=azure:defender-for-cloud sub="Secure score and threat protection" group landing "Landing zones management group" type=cloud icon=azure:management-groups { group sub-prod "Workload production subscription" type=account icon=azure:subscriptions { group rg-net "rg-spoke-network-prod" type=resource-group icon=azure:resource-groups { group spoke "Spoke VNet 10.1.0.0/16" type=network { group pe-subnet "Private endpoint subnet" type=isolated-subnet { node pe-web gateway "Private endpoint: Web App" icon=azure:private-endpoints } } } group rg-app "rg-app-prod" type=resource-group icon=azure:resource-groups { group plan "App Service plan" type=service-plan icon=azure:app-service-plans sub="Premium, zone redundant" { node web service "Web App" icon=azure:app-service sub="React frontend" node func function "Function App" icon=azure:functions sub="API" } node mi service "Managed identity" icon=azure:managed-identities sub="No stored credentials" } group rg-data "rg-data-prod" type=resource-group icon=azure:resource-groups { node pe-kv gateway "Private endpoint: Key Vault" icon=azure:private-endpoints node kv secret "Key Vault" icon=azure:key-vault sub="RBAC, no access policies" node pg database "PostgreSQL Flexible Server" icon=azure:database-for-postgresql sub="Private access, Entra authentication" } } } group platform "Platform management group" type=cloud icon=azure:management-groups { group sub-conn "Connectivity subscription" type=account icon=azure:subscriptions { group rg-hub "rg-hub-network" type=resource-group icon=azure:resource-groups { group hub "Hub VNet 10.0.0.0/16" type=network { node fw gateway "Azure Firewall" icon=azure:firewall sub="Egress and DNS proxy" node resolver gateway "Private DNS resolver" icon=azure:dns-private-resolver } } group rg-dns "rg-private-dns" type=resource-group icon=azure:resource-groups { node dns-pg service "private.postgres.database.azure.com" icon=azure:dns-zones node dns-kv service "privatelink.vaultcore.azure.net" icon=azure:dns-zones node dns-web service "privatelink.azurewebsites.net" icon=azure:dns-zones } } group sub-mgmt "Management subscription" type=account icon=azure:subscriptions { group rg-mon "rg-monitoring" type=resource-group icon=azure:resource-groups { node insights service "Application Insights" icon=azure:application-insights node logs service "Log Analytics workspace" icon=azure:log-analytics } } } } users -> entra : "Sign in" role=identity entra -> mfa : "Evaluate Conditional Access" mfa -> afd : "Token issued, request continues" afd -> pe-web : "Private Link origin" animate=on pe-web -> web : "Frontend" web -> func : "API call with the bearer token" animate=on func -> mi : "Request a token" mi -> pe-kv : "Read secrets, with RBAC" role=identity pe-kv -> kv mi -> pg : "Connect with an Entra token" animate=on entra -> appreg : "Registers the apps" step=off entra -> pim : "Elevates admins" step=off policy -> defender : "Policy results feed the score" step=off func -> fw : "Egress through the hub" step=off fw -> resolver : "DNS proxy" step=off resolver -> dns-pg step=off resolver -> dns-kv step=off resolver -> dns-web step=off func -> insights : "Telemetry" step=off insights -> logs step=off ``` *Picture: Azure landing zone: tenant, subscriptions and resource groups: 22 nodes, 20 connections. The same hub and spoke workload shown with where everything is owned: one Microsoft Entra tenant signs users in with Conditional Access and MFA, a management group tree applies policy, and three subscriptions (connectivity, management and workload) hold the resource groups that contain each resource.* **Text description** **Nodes:** - Users (Browsers), client - Front Door Premium (WAF and global entry), gateway - Identity: Microsoft Entra tenant - Entra ID (Users, groups and sign-in), identity - Conditional Access and MFA (Require MFA and a compliant device), service - Privileged Identity Management (Just-in-time admin roles), service - App registrations (Frontend and API), service - Cloud: Tenant root management group - Azure Policy (Allowed regions, required tags, private only), service - Defender for Cloud (Secure score and threat protection), service - Cloud: Landing zones management group - Account: Workload production subscription - Resource group: rg-spoke-network-prod - Virtual network: Spoke VNet 10.1.0.0/16 - Isolated subnet: Private endpoint subnet - Private endpoint: Web App, gateway - Resource group: rg-app-prod - Managed identity (No stored credentials), service - Service plan: App Service plan (Premium, zone redundant) - Web App (React frontend), service - Function App (API), function - Resource group: rg-data-prod - Private endpoint: Key Vault, gateway - Key Vault (RBAC, no access policies), secret - PostgreSQL Flexible Server (Private access, Entra authentication), database - Cloud: Platform management group - Account: Connectivity subscription - Resource group: rg-hub-network - Virtual network: Hub VNet 10.0.0.0/16 - Azure Firewall (Egress and DNS proxy), gateway - Private DNS resolver, gateway - Resource group: rg-private-dns - private.postgres.database.azure.com, service - privatelink.vaultcore.azure.net, service - privatelink.azurewebsites.net, service - Account: Management subscription - Resource group: rg-monitoring - Application Insights, service - Log Analytics workspace, service **Connections:** - 1. Users to Entra ID (identity line): Sign in - 2. Entra ID to Conditional Access and MFA: Evaluate Conditional Access - 3. Conditional Access and MFA to Front Door Premium: Token issued, request continues - 4. Front Door Premium to Private endpoint: Web App: Private Link origin - 5. Private endpoint: Web App to Web App: Frontend - 6. Web App to Function App: API call with the bearer token - 7. Function App to Managed identity: Request a token - 8. Managed identity to Private endpoint: Key Vault (identity line): Read secrets, with RBAC - 9. Private endpoint: Key Vault to Key Vault - 10. Managed identity to PostgreSQL Flexible Server: Connect with an Entra token - Entra ID to App registrations: Registers the apps - Entra ID to Privileged Identity Management: Elevates admins - Azure Policy to Defender for Cloud: Policy results feed the score - Function App to Azure Firewall: Egress through the hub - Azure Firewall to Private DNS resolver: DNS proxy - Private DNS resolver to private.postgres.database.azure.com - Private DNS resolver to privatelink.vaultcore.azure.net - Private DNS resolver to privatelink.azurewebsites.net - Function App to Application Insights: Telemetry - Application Insights to Log Analytics workspace ## Next steps | Doc | Why | |---|---| | [Examples to copy](https://docs.plotwork.dev/examples.md) | Smaller diagrams, one idea each, with the prompt for each | | [Cloud architecture](https://docs.plotwork.dev/cloud-architecture.md) | Accounts, regions, networks, subnets, and icons for AWS, Azure, and Google Cloud | | [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md) | Numbered steps, animated lines, and the rest of the language | | [Architecture patterns](https://docs.plotwork.dev/architecture-patterns.md) | Checked, ready-made diagrams to start from | ## See also | Doc | Why | |---|---| | [Quick start](https://docs.plotwork.dev/quick-start.md) | Connect an agent and draw a first diagram | | [Group types](https://docs.plotwork.dev/cloud-architecture/group-types.md) | The types of group used here, each drawn | | [Icon names and lookup](https://docs.plotwork.dev/cloud-architecture/icon-names.md) | From a product name to an `icon=` | --- # Plotwork for AI agents > Page: https://docs.plotwork.dev/agents/overview. Index of every page: https://docs.plotwork.dev/llms.txt How an AI agent finds, reads, and uses Plotwork: the files it can read without connecting, the ways to call the tools, and the loop that gets a valid diagram. ## TL;DR | Question | Answer | |---|---| | What is Plotwork? | A small text language and renderer for twenty kinds of diagram, from flow, sequence, and database to Gantt, state, mind map, and treemap, written by AI agents and drawn the same way everywhere: in a README, in your editor, and over MCP. | | How does an agent call it? | Over MCP, locally or hosted, or over HTTP. Every way runs the same 18 tools, which the [tool reference](https://docs.plotwork.dev/mcp/tools.md) lists. | | Which clients work? | Any MCP client. The [connecting guide](https://docs.plotwork.dev/mcp/connect.md) has the settings for Claude Code, Cursor, VS Code, and Claude Desktop, and the [rules file](https://docs.plotwork.dev/agents/rules.md) says where Codex, GitHub Copilot, and Gemini CLI read their instructions. ChatGPT, Claude, and VS Code show a drawing inline. | | What does it cost to start? | The local MCP server needs no key and no account. The hosted MCP server and the HTTP API need an API key, and the hosted MCP server also accepts a browser sign-in. | | Where do I read the docs without a browser? | `https://docs.plotwork.dev/llms.txt` indexes every page. Add `.md` to the address of any page to get it as Markdown. | | Which tool do I call first? | [`get_diagram_syntax`](https://docs.plotwork.dev/mcp/tools/get-diagram-syntax.md) once to read the language, then [`draw_diagram`](https://docs.plotwork.dev/mcp/tools/draw-diagram.md) with the source. For a common architecture, call [`find_pattern`](https://docs.plotwork.dev/mcp/tools/find-pattern.md) first. | | What if the source is wrong? | [`draw_diagram`](https://docs.plotwork.dev/mcp/tools/draw-diagram.md) returns every error with its line, column, and hint, and draws nothing until the source is valid. Fix them all and call again. | ## What the server tells your agent The MCP server sends this guidance to a client when it connects. It is listed here for an agent that reads the docs before it has a connection. The [rules file](https://docs.plotwork.dev/agents/rules.md) holds the same text ready to paste into a project. 1. Draws flow, sequence, database schema, git graph, Gantt chart, class diagram, state, timeline, user journey, mind map, treemap, share chart, quadrant chart, layered architecture, C4 diagram, matrix, Sankey diagram, XY chart, tree, and swimlane diagrams from a small line-oriented text language. 2. Prefer the workflow tools: each does the work of several single tools in one call. 3. When you are unsure which kind of diagram fits, call `suggest_diagram` with the request in plain words and any data: it ranks the kinds and gives a starter that parses; for a whole document, `plan_document_diagrams` returns the sections a picture would help, each with a starter. 4. When the diagram should come from a file the person has (SQL, Prisma, or Drizzle models, an OpenAPI document, Terraform, Bicep, ARM, Pulumi, CloudFormation, or the CDK, docker-compose, Kubernetes manifests, Helm, Kustomize, a Backstage catalog, a Structurizr workspace, AsyncAPI documents, a GitHub Actions workflow, a GitLab CI/CD or Azure Pipelines pipeline, CSV or JSON data, a folder listing, or a git log), call `import_source` with its text and format: it builds the whole diagram from the file in one call, and its diagnostics say what was left out `get_diagram_syntax` with topic import-formats says what each format reads. 5. For a request to draw a common architecture, such as a three-tier network, a serverless API, a container platform, or a queue with workers, call `find_pattern` first with the request in plain words; when a pattern fits, call `draw_from_pattern` with its id, a provider flavour (neutral, aws, azure, or gcp), and slot values that rename its parts, or `get_pattern` to read and edit its source, and when nothing fits, write the diagram from scratch with `draw_diagram`. 6. Call `get_diagram_syntax` once with no kind to read the shared language, and again with kind set to the kind you draw for its statements, limits, and example. 7. Write the source and call `draw_diagram`: it returns every error with line, column, and hint and draws nothing until the source is valid, so fix every error and call again; once valid it returns the warnings, an outline to check the meaning, and the SVG for light and dark. 8. The pictures are most of the answer and you cannot look at them, so pass svg "never" to check a diagram, and draw only when you will save, show, or publish the pictures; when a drawing is too big to read in one answer the SVG is left out, picturesOmitted says so, and svg "always" forces it. 9. To change a diagram that already exists as a Plotwork SVG, a hosted link such as https://cdn.plotwork.dev/d/<22 characters>.svg or an .svg file in the repository, call `read_diagram` first with its url or its text, change the source it returns with `edit_diagram` (rename, set an option, add, remove, or move a statement) or by hand and `draw_diagram`, then write the SVG file again; `compare_diagrams` says what changed, for a review. 10. Set embedSource true on `draw_diagram` whenever you save an SVG into a repository, so the file carries its source and `read_diagram` can give it back next time; the source is then readable by anyone who has the file. 11. To save a valid diagram into the person's repository, run the saveCommand a drawing tool returns in a shell, changing --out to the path you want (add --theme dark for the dark picture): the Plotwork command line reads the diagram from the link on that machine, fetches nothing, and writes the SVG with its source embedded, so you never copy the SVG yourself. 12. With no shell, draw with svg "always" and embedSource true and write the returned svg. 13. Give viewUrl to a person to look at the diagram, and svgUrl or pngUrl to download it in their browser; neither is a file address for curl. 14. An item with no icon= gets the icon its label names when the match is certain, such as the Postgres mark on a node labelled "Postgres", so label items with the product they are; icon=none keeps one item plain and icons=off on the header keeps them all plain. 15. For a finished look, label flow arrows with verbs ("reads", "publishes", "deploys to") so they draw icons, add role= (data, async, identity, private) to the lines that carry those, and give groups a type=; style in `get_diagram_syntax` says more. 16. Use `map_icons` to choose icons for many names at once; it never guesses, so choose from the candidates for an ambiguous name, and never invent an icon. 17. To move Mermaid to Plotwork, call `convert_mermaid` with one diagram, read every diagnostic and its hint, then choose node types and icons; for a whole Markdown file call `sync_markdown` with the document, its path, and convertMermaid true, write the returned markdown and files, then review the diff. 18. When the repository has a plotwork.config.json (or .plotwork/config.json) at its root, read it and pass it as settings to every tool that takes settings: its defaults fill the header options the source leaves out, and broken style rules come back as warnings to fix. 19. Use `validate_diagram`, `outline_diagram`, `render_diagram`, and `search_icons` only for one fine-grained step. 20. Choose node types and tones for meaning; colors are chosen by the theme and cannot be set. ## The tools 11 workflows do a whole job in one call, and 7 single tools do one step. Prefer a workflow. | Tool | Kind | What it does | A request that leads to it | |---|---|---|---| | [`draw_diagram`](https://docs.plotwork.dev/mcp/tools/draw-diagram.md) | Workflow | Checks, describes, and draws a diagram. | Draw a diagram of how a user signs in through an API that checks a database. | | [`edit_diagram`](https://docs.plotwork.dev/mcp/tools/edit-diagram.md) | Workflow | Changes diagram source with typed operations, on every kind, and checks the result. | Rename the Auth API node to gateway and add a session cache it writes to. | | [`convert_mermaid`](https://docs.plotwork.dev/mcp/tools/convert-mermaid.md) | Workflow | Converts one Mermaid diagram (flowchart or graph, sequenceDiagram, erDiagram, gitGraph, gantt, classDiagram, stateDiagram, timeline, journey, mindmap, treemap-beta or treemap, pie, quadrantChart, C4Context or C4Container or C4Component or C4Dynamic or C4Deployment, sankey-beta or sankey, xychart-beta or xychart, treeView-beta or treeView) to Plotwork source. | Convert this Mermaid flowchart to Plotwork. | | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | Workflow | Turns a file into a finished diagram in one call. | Draw our database schema from this SQL. | | [`map_icons`](https://docs.plotwork.dev/mcp/tools/map-icons.md) | Workflow | Finds an icon for each of up to 40 service or product names in one call, such as the services of an architecture. | Which icons should I use for S3, Lambda, Postgres and a queue on AWS? | | [`sync_markdown`](https://docs.plotwork.dev/mcp/tools/sync-markdown.md) | Workflow | Turns the diagram fences of a Markdown document into pictures, as data. | Replace the Mermaid diagrams in README.md with Plotwork diagrams. | | [`suggest_diagram`](https://docs.plotwork.dev/mcp/tools/suggest-diagram.md) | Workflow | Picks the kind of diagram for a request in plain words, such as "permission matrix for these endpoints and roles" or "where our cloud spend goes", with optional pasted data (a table, a list, numbers by period). | Which diagram should I use to show who can call which endpoint? | | [`plan_document_diagrams`](https://docs.plotwork.dev/mcp/tools/plan-document-diagrams.md) | Workflow | Reads a whole document (Markdown, HTML, or plain text pasted from Confluence, Notion, or Google Docs), splits it by its headings, and returns the sections a picture would help. | Which parts of this design doc need a diagram? | | [`find_pattern`](https://docs.plotwork.dev/mcp/tools/find-pattern.md) | Workflow | Finds a ready-made, checked architecture diagram for a request such as "three tier network", "serverless api", or "two availability zones". | Draw a three-tier network with load balancers. | | [`get_pattern`](https://docs.plotwork.dev/mcp/tools/get-pattern.md) | Workflow | Returns one pattern filled for a flavour. | Show me the three-tier network pattern for Azure and rename the office network to Branch office. | | [`draw_from_pattern`](https://docs.plotwork.dev/mcp/tools/draw-from-pattern.md) | Workflow | Fills a pattern and draws it in one call. | Draw a two-zone network on AWS with a replicated database. | | [`read_diagram`](https://docs.plotwork.dev/mcp/tools/read-diagram.md) | Single tool | Read the source of an existing Plotwork SVG so you can edit the diagram. | One step of a larger job. | | [`compare_diagrams`](https://docs.plotwork.dev/mcp/tools/compare-diagrams.md) | Single tool | Compare two versions of a diagram by meaning, for a pull request review. | One step of a larger job. | | [`get_diagram_syntax`](https://docs.plotwork.dev/mcp/tools/get-diagram-syntax.md) | Single tool | Read the diagram language as data. | One step of a larger job. | | [`validate_diagram`](https://docs.plotwork.dev/mcp/tools/validate-diagram.md) | Single tool | Check diagram source without drawing it: every error in one pass, each with line, column, and a fix hint, plus quality warnings. | One step of a larger job. | | [`render_diagram`](https://docs.plotwork.dev/mcp/tools/render-diagram.md) | Single tool | Render valid source to a self-contained, accessible SVG in the light or dark theme, with its pixel size and quality warnings. | One step of a larger job. | | [`outline_diagram`](https://docs.plotwork.dev/mcp/tools/outline-diagram.md) | Single tool | Describe valid diagram source as plain text. | One step of a larger job. | | [`search_icons`](https://docs.plotwork.dev/mcp/tools/search-icons.md) | Single tool | Find bundled icon names by keyword, idea, product, or abbreviation ("postgres", "token exchange", "s3", "cosmos db"). | One step of a larger job. | ## Ways to call them | Way | Address | What it needs | |---|---|---| | Local MCP server | `npx -y @buun_group/plotwork-mcp@0.1.0-beta.10` | Node 22 or later. No key, no account, and no network unless you opt in to usage sharing. | | Hosted MCP server, with a key | `https://mcp.plotwork.dev/mcp` | An API key in the `X-API-Key` header or as a bearer value. | | Hosted MCP server, with a sign-in | `https://mcp.plotwork.dev/mcp-oauth` | A Plotwork account. The client opens a browser and you approve the connection. | | HTTP API | `https://api.plotwork.dev/v1` | An API key, sent the same way. Each tool has a route, listed in the [rules file](https://docs.plotwork.dev/agents/rules.md#without-mcp). | The [connecting guide](https://docs.plotwork.dev/mcp/connect.md) has the exact setting for each client. ## See also | Doc | Why | |---|---| | [llms.txt and Markdown pages](https://docs.plotwork.dev/agents/machine-readable.md) | Every file an agent can read without a browser, and when to fetch each | | [Rules file for your agent](https://docs.plotwork.dev/agents/rules.md) | The guidance above, ready to paste into CLAUDE.md or AGENTS.md | | [Prompts and recipes](https://docs.plotwork.dev/agents/prompts.md) | Requests that work and the tools an agent calls for each | | [Connecting an agent](https://docs.plotwork.dev/mcp/connect.md) | Setting up Claude Code, Cursor, VS Code, and other clients | | [Language reference](https://docs.plotwork.dev/reference/language.md) | Every word the diagram language accepts | --- # llms.txt and Markdown pages > Page: https://docs.plotwork.dev/agents/machine-readable. Index of every page: https://docs.plotwork.dev/llms.txt The files that let an agent read these docs without a browser: an index, the full text, a Markdown copy of every page, and the generated references as data. ## TL;DR | Question | Answer | |---|---| | Where do I start? | `https://docs.plotwork.dev/llms.txt`. It lists every page with a line on what it holds and its size in tokens. | | How do I get one page as Markdown? | Add `.md` to its address, for example `https://docs.plotwork.dev/quick-start.md`. | | How do I load a whole subject? | Fetch the bundle of its tab, `https://docs.plotwork.dev/llms-<tab>.txt`. It is the recommended way, and `llms.txt` gives the size of each bundle in tokens. | | How do I load all of it? | `https://docs.plotwork.dev/llms-full.txt` holds every page that is not optional. It is the largest file, meant for a search index and not for a task. | | Where are the schemas? | `https://docs.plotwork.dev/reference/tools.json` for the tools and `https://docs.plotwork.dev/reference/openapi.json` for the HTTP API. | | Can a crawler read the pages? | Yes. The robots file welcomes search engines and AI assistants by name, every page is also served as HTML with its text in the file, and a sitemap lists every address. | ## Files Every file below is written when the docs are built, from the same pages a reader sees, so none can differ from them. | File | Address | What it holds | Fetch it when | |---|---|---|---| | `llms.txt` | `https://docs.plotwork.dev/llms.txt` | An index of every page: its title, address, a line on what it holds, and its size in tokens. | You do not know which page you need. Read this first. | | `llms-docs.txt` | `https://docs.plotwork.dev/llms-docs.txt` | The text of every page in the Documentation tab except the optional ones, in one file. | You need all of Documentation in one fetch. A tab bundle is the recommended way to load a whole subject. | | `llms-agents.txt` | `https://docs.plotwork.dev/llms-agents.txt` | The text of every page in the AI agents tab except the optional ones, in one file. | You need all of AI agents in one fetch. A tab bundle is the recommended way to load a whole subject. | | `llms-mcp.txt` | `https://docs.plotwork.dev/llms-mcp.txt` | The text of every page in the MCP tab except the optional ones, in one file. | You need all of MCP in one fetch. A tab bundle is the recommended way to load a whole subject. | | `llms-cli.txt` | `https://docs.plotwork.dev/llms-cli.txt` | The text of every page in the CLI tab except the optional ones, in one file. | You need all of CLI in one fetch. A tab bundle is the recommended way to load a whole subject. | | `llms-api.txt` | `https://docs.plotwork.dev/llms-api.txt` | The text of every page in the API reference tab except the optional ones, in one file. | You need all of API reference in one fetch. A tab bundle is the recommended way to load a whole subject. | | `llms-reference.txt` | `https://docs.plotwork.dev/llms-reference.txt` | The text of every page in the Language tab except the optional ones, in one file. | You need all of Language in one fetch. A tab bundle is the recommended way to load a whole subject. | | `llms-full.txt` | `https://docs.plotwork.dev/llms-full.txt` | The text of every page except the optional ones, in one file. It is the largest file here. | You are loading the whole documentation into a search index. For a task, read a tab bundle instead. | | `tools.json` | `https://docs.plotwork.dev/reference/tools.json` | The name, description, behavior hints, and JSON Schema of every tool. | You build a client or a tool call and need the exact schema. | | `openapi.json` | `https://docs.plotwork.dev/reference/openapi.json` | The OpenAPI 3.1 document of the HTTP API. | You call the HTTP API and want a generated client or the exact request and response shapes. | | `sitemap.xml` | `https://docs.plotwork.dev/sitemap.xml` | The address of every page. | You crawl the site. | | `robots.txt` | `https://docs.plotwork.dev/robots.txt` | What crawlers may fetch, and where the sitemap is. | You run a crawler. | ## One tab in one fetch Each tab of the docs has a bundle that holds the text of its pages, except the pages marked optional, which are catalogues and policies. Each page starts with its own title and the address it is served at, so an agent can cite it. | Bundle | Tab | Pages in it | |---|---|---| | `llms-docs.txt` | Documentation | 35 | | `llms-agents.txt` | AI agents | 5 | | `llms-mcp.txt` | MCP | 25 | | `llms-cli.txt` | CLI | 20 | | `llms-api.txt` | API reference | 132 | | `llms-reference.txt` | Language | 10 | ## Markdown copies A page copy is not the source file of the page. It is the page as an agent should read it. - A link to another page of the docs is the full address of that page's Markdown copy, so an agent can follow it without a browser. - A diagram keeps its source in a fenced block, and its picture is replaced by the one sentence that describes it, such as "Login request: 3 nodes, 3 connections." - Icons that only decorate a table are left out, and the sync comments and collapsible wrappers of the page are gone. - The first line under the title names the page and the index, because a copy fetched on its own has no sidebar around it. ## A good reading order 1. Fetch `llms.txt` and pick pages by title and size. A page of 2 thousand tokens costs less than guessing. 2. Fetch only those pages as Markdown. 3. Fetch a `llms-<tab>.txt` bundle when the task needs a whole subject, such as every tool or the whole language reference. Prefer it to `llms-full.txt`. 4. Read the optional pages last, and only when the task needs them: icon catalogues, the architecture pattern gallery, and the policies. ## See also | Doc | Why | |---|---| | [Plotwork for AI agents](https://docs.plotwork.dev/agents/overview.md) | What an agent needs to know in one page | | [Rules file for your agent](https://docs.plotwork.dev/agents/rules.md) | Guidance to paste into a project | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The tools whose schemas `tools.json` holds | | [API reference](https://docs.plotwork.dev/api/reference.md) | The endpoints whose document `openapi.json` holds | --- # The Claude Code plugin > Page: https://docs.plotwork.dev/agents/claude-code-plugin. Index of every page: https://docs.plotwork.dev/llms.txt The Plotwork plugin for Claude Code joins the MCP server and the command line, so a diagram you ask for ends up as an SVG in your docs with no step of your own. It is for anyone who uses Claude Code in a repository with documentation. ## TL;DR | Question | Answer | |---|---| | What does it add? | The local MCP server, a hook that draws diagrams with the command line every time Claude saves one, a gallery on localhost that reloads, and three skills that tell Claude how to use them together. | | What if Claude starts to write Mermaid? | The hook refuses a new Mermaid, PlantUML, Graphviz, or D2 block in a documentation file and tells Claude which Plotwork tools to use instead. `PLOTWORK_MERMAID=warn` or `allow` relaxes it. | | How do I install it? | `/plugin marketplace add plotwork-official/plotwork-claude-plugin`, then `/plugin install plotwork@plotwork`. | | What happens when Claude saves a `.diagram` file? | The hook runs `plotwork render` and writes the SVG beside it, with the source inside. | | What happens when Claude saves a Markdown page with a `diagram` fence? | The hook runs `plotwork sync`: a light and a dark SVG land in `assets/diagrams/` beside the page, and the picture block is written under the fence. | | What if the diagram has a mistake? | Nothing is written. Claude gets each mistake with its line, column, and hint, fixes it, and saves again. | | How do I see the pictures? | Ask Claude to preview them, or run `/plotwork:preview`. The gallery is on `http://127.0.0.1:3900` and reloads within a second of a change. | | Does it need an account or a key? | No. The MCP server, the command line, and the gallery all run on your machine. | | Does it use the network? | `npx` downloads the MCP server and the command line from npm the first time. After that, nothing is sent by default. | ## How the pieces fit The MCP tools write and check the source. The command line draws the pictures on disk. The hook runs the command line the moment Claude saves a file, so the SVG never passes through the model and the picture is the one CI draws. **Diagram source** ```diagram sequence "Plugin save loop" desc="Claude writes the source with the MCP tools, saves it, and the hook draws the SVG with the command line." actor you person "You" actor claude agent "Claude Code" actor mcp service "Plotwork MCP" sub="draw_diagram" actor hook function "Plugin hook" sub="After each save" actor cli service "Command line" sub="render or sync" you -> claude : "Add a diagram of checkout to the docs" claude -> mcp : "Check the source" mcp --> claude : "Valid, with an outline" claude -> hook : "Saves the page or .diagram file" hook -> cli : "Draw it" alt "the diagram is valid" { cli --> hook : "SVGs written" hook --> claude : "Files drawn" } else "it has a mistake" { cli --> hook : "Line, column, hint" hook --> claude : "Blocked: fix and save again" } claude --> you : "Diagram is in the docs" ``` *Picture: Plugin save loop: 5 participants. Claude writes the source with the MCP tools, saves it, and the hook draws the SVG with the command line.* **Text description** **Participants:** - You, person - Claude Code, agent - Plotwork MCP (draw_diagram), service - Plugin hook (After each save), function - Command line (render or sync), service **Steps:** - 1. You to Claude Code: Add a diagram of checkout to the docs - 2. Claude Code to Plotwork MCP: Check the source - 3. Plotwork MCP to Claude Code (dashed): Valid, with an outline - 4. Claude Code to Plugin hook: Saves the page or .diagram file - 5. Plugin hook to Command line: Draw it - alt: the diagram is valid - 6. Command line to Plugin hook (dashed): SVGs written - 7. Plugin hook to Claude Code (dashed): Files drawn - else: it has a mistake - 8. Command line to Plugin hook (dashed): Line, column, hint - 9. Plugin hook to Claude Code (dashed): Blocked: fix and save again - 10. Claude Code to You (dashed): Diagram is in the docs ## 1. Install the plugin Inside Claude Code: ```text /plugin marketplace add plotwork-official/plotwork-claude-plugin /plugin install plotwork@plotwork ``` Or name the marketplace in the install itself: ```text /plugin install plotwork --marketplace plotwork-official/plotwork-claude-plugin ``` The repository [plotwork-official/plotwork-claude-plugin](https://github.com/plotwork-official/plotwork-claude-plugin) holds the plugin and its marketplace, and is updated with each change to the plugin. To update an installed copy, run `/plugin marketplace update plotwork`. Restart Claude Code when it asks. Run `/mcp` to confirm that the `plotwork` server is connected. The plugin needs Node 22 or newer. If you already added the server with `claude mcp add plotwork`, remove that one with `claude mcp remove plotwork` so Claude sees one set of tools. ## 2. Ask for a diagram Ask in plain words, and say where it goes: | You say | What Claude does | |---|---| | "Add a diagram of the checkout flow to `docs/architecture.md`." | Writes a `diagram` fence into the page. The hook syncs it. | | "Draw our Terraform in `infra/` as a diagram for the docs site." | Calls `import_source`, saves `docs/diagrams/infra.diagram`. The hook writes `infra.svg`. | | "Illustrate the README." | Calls `plan_document_diagrams`, adds a fence for each section that needs one, and saves. | | "Move the Mermaid diagrams in `docs/` to Plotwork." | Calls `sync_markdown` with `convertMermaid` for each page. | | "Show me the diagrams." | Starts the gallery and gives you its address. | Claude picks these up from the skills without being told their names. You can also run one yourself: `/plotwork:draw`, `/plotwork:document`, or `/plotwork:preview`. ## 3. Where the pictures go | What Claude saved | What the hook writes | How the docs show it | |---|---|---| | A fence in `docs/guide.md` | `docs/assets/diagrams/<id>.svg` and `<id>.dark.svg`, and the picture block under the fence | The picture block is a `<picture>` element that follows the reader's light or dark setting | | `docs/diagrams/checkout.diagram` | `docs/diagrams/checkout.svg`, with the source embedded | Link it from any page with a Markdown image whose path is `diagrams/checkout.svg`, relative to the page | Commit the SVGs with the page. Never edit the picture block by hand; the next sync rewrites it. The [Markdown and GitHub](https://docs.plotwork.dev/markdown-and-github.md) guide explains the picture block. The command line reads your [repository settings](https://docs.plotwork.dev/repository-settings.md) on every run, so header defaults in `plotwork.config.json` apply to the hook's pictures. At the start of a session the plugin tells Claude the file is there, so the MCP tools use it too. ## 4. Preview on localhost The gallery shows every SVG under the project, grouped by folder, with each light picture beside its dark one. It skips `node_modules`, `.git`, and build output. | Setting | Default | Change it | |---|---|---| | Folder | The project root | Ask for one folder, such as "preview `docs`" | | Port | 3900, or the next free one of the ten after it | Ask for a port | | Address | 127.0.0.1 only | Not changeable; it is never reachable from another machine | Pictures are shown in `<img>` elements, where a script inside an SVG never runs, and the server answers only for `.svg` files. ## 5. Mermaid and other diagram languages Claude knows Mermaid well, so it reaches for it by habit. The plugin steers it to Plotwork at three points, and none of them repeats on every turn. | When | What the plugin does | |---|---| | The session starts | One sentence in the session note: draw diagrams with the Plotwork tools, never Mermaid, PlantUML, Graphviz, or D2. | | Your prompt asks for a diagram | Two sentences of guidance, only when the prompt names a diagram as a whole word: diagram, flowchart, flow chart, sequence diagram, architecture diagram, ERD, entity relationship, Mermaid, mind map, Gantt, state machine, C4, swimlane, or org chart. "Diagrammatic" does not count. | | Claude is about to save a new block | The write is refused before it happens, with what to do instead. | ### What is refused A write is refused when it adds a block in another diagram language to a documentation file: | Part | What counts | |---|---| | Languages | A fence of `mermaid`, `plantuml`, `puml`, `dot`, `graphviz`, or `d2`, with backticks or tildes; a `<div class="mermaid">` or `<pre class="mermaid">` element; a Sphinx `.. mermaid::`, `.. graphviz::`, or `.. uml::` directive. | | Files | `.md`, `.mdx`, `.markdown`, `.rst`, `.txt`, a markdown cell of an `.ipynb` notebook, and a README or CHANGELOG with no extension. Code and data files are never read, so a test that holds Mermaid in a string is left alone. | | Folders left alone | `node_modules`, `.git`, build output, `vendor`, and test fixtures (`fixtures`, `__fixtures__`, `__tests__`, `testdata`, `__snapshots__`). | | Only new blocks | An edit whose old text already held the block goes through, and so does a rewrite of a page that keeps the blocks it had. Working near an existing Mermaid diagram is never blocked. | Claude reads this reason, and in practice follows it in the same turn: ```text This page is drawn with Plotwork, so write a ```diagram fence in Plotwork's language instead of mermaid. Call mcp__plugin_plotwork_plotwork__get_diagram_syntax once if you have not read the language, check the source with mcp__plugin_plotwork_plotwork__draw_diagram (svg "never"), and the plugin syncs the fence into a picture when you save. To keep this Mermaid, call mcp__plugin_plotwork_plotwork__convert_mermaid with it and write the Plotwork source it returns; for a whole page of Mermaid, call mcp__plugin_plotwork_plotwork__sync_markdown with convertMermaid true. If the person asked for mermaid by name, tell them the Plotwork plugin refused it and that PLOTWORK_MERMAID=allow lets it through. ``` ### When you want Mermaid | `PLOTWORK_MERMAID` | Effect | |---|---| | `convert`, or unset | The default. A new block is refused with the reason above. | | `warn` | The write goes through, and Claude gets the same reason as a note. | | `allow` | The write goes through with nothing said, and the session and prompt notes leave Mermaid out. | To set it for one repository, put it in the `env` of the repository's `.claude/settings.json`, which Claude Code passes to the hook: ```json { "env": { "PLOTWORK_MERMAID": "warn" } } ``` It is not a field of `plotwork.config.json`: every released command line, Action, and MCP server checks that file strictly, and would refuse a field it does not know. ### Without the plugin If you added the MCP server with `claude mcp add` and do not want the plugin, this hook in your `.claude/settings.json` refuses a new Mermaid fence in a Markdown file in the same way. It is plain Node with nothing to install. The plugin does more: it also covers PlantUML, Graphviz, D2, notebooks, and fixtures, adds the prompt and session notes, and draws the fences on save, so prefer the plugin when you can install it. ```json { "hooks": { "PreToolUse": [ { "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "node -e \"let s='';process.stdin.on('data',c=>s+=c).on('end',()=>{const fs=require('fs'),e=JSON.parse(s),i=e.tool_input||{},f=String(i.file_path||'');if(!['.md','.mdx','.markdown'].some(x=>f.toLowerCase().endsWith(x)))return;const n=t=>(String(t||'').match(/^ {0,3}(\\x60{3,}|~{3,})[ \\t]*mermaid\\b/gim)||[]).length;const old=e.tool_name==='Write'&&fs.existsSync(f)?fs.readFileSync(f,'utf8'):'';const edits=i.edits||[{old_string:i.old_string??old,new_string:i.new_string??i.content}];if(edits.some(x=>n(x.new_string)>n(x.old_string)))console.log(JSON.stringify({hookSpecificOutput:{hookEventName:'PreToolUse',permissionDecision:'deny',permissionDecisionReason:'Draw this with Plotwork instead of Mermaid: write a diagram fence in the Plotwork language and check it with the draw_diagram tool, or call convert_mermaid on this Mermaid and use the source it returns. For a whole page of Mermaid, call sync_markdown with convertMermaid true.'}}))})\"", "timeout": 5 } ] } ] } } ``` ## 6. Settings | Variable | Effect | |---|---| | `PLOTWORK_HOOKS=off` | Turns the hook off, including the Mermaid guard. The skills then run the command line themselves. | | `PLOTWORK_MERMAID` | `convert` (the default), `warn`, or `allow`. See [When you want Mermaid](#when-you-want-mermaid). | | `PLOTWORK_CLI` | The command the hook runs instead of the default, such as `yarn plotwork`. It is split on spaces. | | `PLOTWORK_PUBLISH=library` | Off unless set. After a drawing that worked, the hook also saves the file's diagrams to an organisation's private library and gives the agent the link. See [Save to your organisation's library](#save-to-your-organisations-library). | | `PLOTWORK_API_KEY`, `PLOTWORK_ORG` | The key the library save uses, and the organisation it saves to. Without `PLOTWORK_ORG`, the hook uses `brand.organization` from `plotwork.config.json`. | Without `PLOTWORK_CLI`, the hook runs the `plotwork` your project installed in `node_modules`, and otherwise `npx -y @buun_group/plotwork@0.1.0-beta.13`, the release this plugin pins. Pin the same release in CI, as [Keep diagrams in sync in CI](https://docs.plotwork.dev/diagrams-in-ci.md) shows, so the check draws what the hook drew. ### Save to your organisation's library Set `PLOTWORK_PUBLISH=library`, `PLOTWORK_API_KEY` and `PLOTWORK_ORG` in the environment Claude Code starts with, or in the `env` of your Claude Code settings. Each time the hook draws a `.diagram` file or syncs a page, it then runs `plotwork publish <file> --org <org>` and adds the link to the note the agent reads, so the agent can hand it to you. Only members of the organisation can open it. | Question | Answer | |---|---| | Can it publish to a public link? | No. The hook always names the organisation, and does nothing when none is named. Use `plotwork publish` yourself for a public link. | | What if the save fails? | The note says why, and nothing is blocked: the files are still drawn in the repository. | | Which command line does it need? | A release that has `publish`. Set `PLOTWORK_CLI` to a newer one if the pinned release is older. | ## Troubleshooting | What you see | Why | Fix | |---|---|---| | Claude says the hook could not run `npx` | No Node on the path, or no network for the first download | Install Node 22, or install the command line in the project with `npm install --save-dev @buun_group/plotwork@0.1.0-beta.13` | | The first save takes a few seconds | `npx` is downloading the command line | Later saves use the cached copy | | A picture is stale after you edit by hand | The hook runs only when Claude saves | Run `npx -y @buun_group/plotwork@0.1.0-beta.13 sync <page>` | | A Mermaid block you asked for was refused | The guard refuses new Mermaid in documentation by default | Ask Claude to keep it, and set `PLOTWORK_MERMAID=allow` or `warn` in the `env` of `.claude/settings.json` | | The tools appear twice | The server is added both by the plugin and by `claude mcp add` | `claude mcp remove plotwork` | ## See also | Doc | Why | |---|---| | [Connecting an agent](https://docs.plotwork.dev/mcp/connect.md) | Add the MCP server to other clients, or use the hosted server | | [Command line overview](https://docs.plotwork.dev/cli/overview.md) | Every command the hook and the skills run | | [Markdown and GitHub](https://docs.plotwork.dev/markdown-and-github.md) | How a fence and its picture block look in a page | | [Migrating from Mermaid](https://docs.plotwork.dev/migrating-from-mermaid.md) | What `convert_mermaid` and `sync_markdown` do with existing Mermaid | | [Keep diagrams in sync in CI](https://docs.plotwork.dev/diagrams-in-ci.md) | Fail a pull request whose pictures are stale | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop the skills follow | --- # Rules file for your agent > Page: https://docs.plotwork.dev/agents/rules. Index of every page: https://docs.plotwork.dev/llms.txt A block to paste into CLAUDE.md, AGENTS.md, or an editor rules file so an agent draws diagrams with the Plotwork tools in the right order. It is the same guidance the MCP server sends when a client connects. ## TL;DR | Question | Answer | |---|---| | What do I paste? | The block below, as it is. | | Where does it go? | In the rules file your agent reads: the table below names the file for each client. | | Do I need the MCP server for it to work? | The block names the tools, so connect them first. See [connecting an agent](https://docs.plotwork.dev/mcp/connect.md). Without MCP, an agent can call each tool over HTTP. | | Does it stay current? | It holds the guidance the server sends and the names of its tools, so it changes when they do. Paste it again after an upgrade. | ## The block Paste this into the rules file of your project: ```markdown ## Diagrams with Plotwork Plotwork draws flow, sequence, database, git graph, Gantt, class, state, timeline, user journey, and mind map diagrams from a small text language. Use its tools for every diagram. Do not draw one by hand and do not invent an icon name. - Draws flow, sequence, database schema, git graph, Gantt chart, class diagram, state, timeline, user journey, mind map, treemap, share chart, quadrant chart, layered architecture, C4 diagram, matrix, Sankey diagram, XY chart, tree, and swimlane diagrams from a small line-oriented text language. - Prefer the workflow tools: each does the work of several single tools in one call. - When you are unsure which kind of diagram fits, call suggest_diagram with the request in plain words and any data: it ranks the kinds and gives a starter that parses; for a whole document, plan_document_diagrams returns the sections a picture would help, each with a starter. - When the diagram should come from a file the person has (SQL, Prisma, or Drizzle models, an OpenAPI document, Terraform, Bicep, ARM, Pulumi, CloudFormation, or the CDK, docker-compose, Kubernetes manifests, Helm, Kustomize, a Backstage catalog, a Structurizr workspace, AsyncAPI documents, a GitHub Actions workflow, a GitLab CI/CD or Azure Pipelines pipeline, CSV or JSON data, a folder listing, or a git log), call import_source with its text and format: it builds the whole diagram from the file in one call, and its diagnostics say what was left out get_diagram_syntax with topic import-formats says what each format reads. - For a request to draw a common architecture, such as a three-tier network, a serverless API, a container platform, or a queue with workers, call find_pattern first with the request in plain words; when a pattern fits, call draw_from_pattern with its id, a provider flavour (neutral, aws, azure, or gcp), and slot values that rename its parts, or get_pattern to read and edit its source, and when nothing fits, write the diagram from scratch with draw_diagram. - Call get_diagram_syntax once with no kind to read the shared language, and again with kind set to the kind you draw for its statements, limits, and example. - Write the source and call draw_diagram: it returns every error with line, column, and hint and draws nothing until the source is valid, so fix every error and call again; once valid it returns the warnings, an outline to check the meaning, and the SVG for light and dark. - The pictures are most of the answer and you cannot look at them, so pass svg "never" to check a diagram, and draw only when you will save, show, or publish the pictures; when a drawing is too big to read in one answer the SVG is left out, picturesOmitted says so, and svg "always" forces it. - To change a diagram that already exists as a Plotwork SVG, a hosted link such as https://cdn.plotwork.dev/d/<22 characters>.svg or an .svg file in the repository, call read_diagram first with its url or its text, change the source it returns with edit_diagram (rename, set an option, add, remove, or move a statement) or by hand and draw_diagram, then write the SVG file again; compare_diagrams says what changed, for a review. - Set embedSource true on draw_diagram whenever you save an SVG into a repository, so the file carries its source and read_diagram can give it back next time; the source is then readable by anyone who has the file. - To save a valid diagram into the person's repository, run the saveCommand a drawing tool returns in a shell, changing --out to the path you want (add --theme dark for the dark picture): the Plotwork command line reads the diagram from the link on that machine, fetches nothing, and writes the SVG with its source embedded, so you never copy the SVG yourself. - With no shell, draw with svg "always" and embedSource true and write the returned svg. - Give viewUrl to a person to look at the diagram, and svgUrl or pngUrl to download it in their browser; neither is a file address for curl. - An item with no icon= gets the icon its label names when the match is certain, such as the Postgres mark on a node labelled "Postgres", so label items with the product they are; icon=none keeps one item plain and icons=off on the header keeps them all plain. - For a finished look, label flow arrows with verbs ("reads", "publishes", "deploys to") so they draw icons, add role= (data, async, identity, private) to the lines that carry those, and give groups a type=; style in get_diagram_syntax says more. - Use map_icons to choose icons for many names at once; it never guesses, so choose from the candidates for an ambiguous name, and never invent an icon. - To move Mermaid to Plotwork, call convert_mermaid with one diagram, read every diagnostic and its hint, then choose node types and icons; for a whole Markdown file call sync_markdown with the document, its path, and convertMermaid true, write the returned markdown and files, then review the diff. - When the repository has a plotwork.config.json (or .plotwork/config.json) at its root, read it and pass it as settings to every tool that takes settings: its defaults fill the header options the source leaves out, and broken style rules come back as warnings to fix. - Use validate_diagram, outline_diagram, render_diagram, and search_icons only for one fine-grained step. - Choose node types and tones for meaning; colors are chosen by the theme and cannot be set. Tools: draw_diagram, edit_diagram, convert_mermaid, import_source, map_icons, sync_markdown, suggest_diagram, plan_document_diagrams, find_pattern, get_pattern, draw_from_pattern, read_diagram, compare_diagrams, get_diagram_syntax, validate_diagram, render_diagram, outline_diagram, search_icons. Documentation for agents: https://docs.plotwork.dev/llms.txt ``` ## Where to put it | Client | File | |---|---| | Claude Code | `CLAUDE.md` in the project, or `~/.claude/CLAUDE.md` for every project | | Codex and most other coding agents | `AGENTS.md` in the project | | Cursor | A rule in `.cursor/rules/`, for example `.cursor/rules/plotwork.mdc` | | GitHub Copilot | `.github/copilot-instructions.md` | | Gemini CLI | `GEMINI.md` in the project | ## Without MCP Each tool is also an HTTP route. Send the arguments as the JSON body of a `POST`, or as the query of a `GET`, with an API key in the `X-API-Key` header. The same checks run, and the answer has the same shape as the tool's result. | Tool | Method | Address | |---|---|---| | [`draw_diagram`](https://docs.plotwork.dev/mcp/tools/draw-diagram.md) | `POST` | `https://api.plotwork.dev/v1/diagrams/draw` | | [`edit_diagram`](https://docs.plotwork.dev/mcp/tools/edit-diagram.md) | `POST` | `https://api.plotwork.dev/v1/diagrams/edit` | | [`convert_mermaid`](https://docs.plotwork.dev/mcp/tools/convert-mermaid.md) | `POST` | `https://api.plotwork.dev/v1/diagrams/convert-mermaid` | | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | `POST` | `https://api.plotwork.dev/v1/import` | | [`map_icons`](https://docs.plotwork.dev/mcp/tools/map-icons.md) | `POST` | `https://api.plotwork.dev/v1/icons/map` | | [`sync_markdown`](https://docs.plotwork.dev/mcp/tools/sync-markdown.md) | `POST` | `https://api.plotwork.dev/v1/markdown/sync` | | [`suggest_diagram`](https://docs.plotwork.dev/mcp/tools/suggest-diagram.md) | `POST` | `https://api.plotwork.dev/v1/diagrams/suggest` | | [`plan_document_diagrams`](https://docs.plotwork.dev/mcp/tools/plan-document-diagrams.md) | `POST` | `https://api.plotwork.dev/v1/markdown/plan` | | [`find_pattern`](https://docs.plotwork.dev/mcp/tools/find-pattern.md) | `POST` | `https://api.plotwork.dev/v1/patterns/find` | | [`get_pattern`](https://docs.plotwork.dev/mcp/tools/get-pattern.md) | `POST` | `https://api.plotwork.dev/v1/patterns/get` | | [`draw_from_pattern`](https://docs.plotwork.dev/mcp/tools/draw-from-pattern.md) | `POST` | `https://api.plotwork.dev/v1/patterns/draw` | | [`read_diagram`](https://docs.plotwork.dev/mcp/tools/read-diagram.md) | `POST` | `https://api.plotwork.dev/v1/diagrams/read` | | [`compare_diagrams`](https://docs.plotwork.dev/mcp/tools/compare-diagrams.md) | `POST` | `https://api.plotwork.dev/v1/diagrams/compare` | | [`get_diagram_syntax`](https://docs.plotwork.dev/mcp/tools/get-diagram-syntax.md) | `GET` | `https://api.plotwork.dev/v1/vocabulary` | | [`validate_diagram`](https://docs.plotwork.dev/mcp/tools/validate-diagram.md) | `POST` | `https://api.plotwork.dev/v1/diagrams/validate` | | [`render_diagram`](https://docs.plotwork.dev/mcp/tools/render-diagram.md) | `POST` | `https://api.plotwork.dev/v1/diagrams/render` | | [`outline_diagram`](https://docs.plotwork.dev/mcp/tools/outline-diagram.md) | `POST` | `https://api.plotwork.dev/v1/diagrams/outline` | | [`search_icons`](https://docs.plotwork.dev/mcp/tools/search-icons.md) | `GET` | `https://api.plotwork.dev/v1/icons` | ## See also | Doc | Why | |---|---| | [Plotwork for AI agents](https://docs.plotwork.dev/agents/overview.md) | The same guidance with the tools described | | [Prompts and recipes](https://docs.plotwork.dev/agents/prompts.md) | Requests that work and the tools an agent calls for each | | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, with its request and response | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop in practice | --- # Prompts and recipes > Page: https://docs.plotwork.dev/agents/prompts. Index of every page: https://docs.plotwork.dev/llms.txt Requests that work well, the tool an agent calls for each, and the jobs an agent is most often given with the calls that do them. ## TL;DR | Question | Answer | |---|---| | How do I phrase a request? | Say what the diagram shows and who it is for, in plain words. The agent chooses the tool, the node types, and the icons. | | Do I name the tools? | No. A connected agent reads each tool's description and picks the right one. | | What if the first drawing is wrong? | Say what is wrong in words, such as "put the cache between the API and the database". The agent edits the source and draws again. | | What does an agent do when a name is ambiguous? | It asks [`map_icons`](https://docs.plotwork.dev/mcp/tools/map-icons.md) for candidates and chooses from them. It never guesses an icon name. | ## Prompts that work Each request below leads to the tool beside it. The last column says what the tool runs. | Request | Tool | What it runs | |---|---|---| | Draw a diagram of how a user signs in through an API that checks a database. | [`draw_diagram`](https://docs.plotwork.dev/mcp/tools/draw-diagram.md) | `validate`, `outline` when needed, `render` when needed | | Check this diagram and draw it in light and dark. | [`draw_diagram`](https://docs.plotwork.dev/mcp/tools/draw-diagram.md) | `validate`, `outline` when needed, `render` when needed | | Rename the Auth API node to gateway and add a session cache it writes to. | [`edit_diagram`](https://docs.plotwork.dev/mcp/tools/edit-diagram.md) | `parse` when needed, `outline` when needed, `validate` when needed, `render` when needed | | Move the database into the data group and remove the old queue. | [`edit_diagram`](https://docs.plotwork.dev/mcp/tools/edit-diagram.md) | `parse` when needed, `outline` when needed, `validate` when needed, `render` when needed | | Convert this Mermaid flowchart to Plotwork. | [`convert_mermaid`](https://docs.plotwork.dev/mcp/tools/convert-mermaid.md) | `convertMermaid`, `validate` when needed, `outline` when needed, `render` when needed | | Migrate this Mermaid diagram to Plotwork and tell me what was lost. | [`convert_mermaid`](https://docs.plotwork.dev/mcp/tools/convert-mermaid.md) | `convertMermaid`, `validate` when needed, `outline` when needed, `render` when needed | | Draw our database schema from this SQL. | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | `importSource`, `validate` when needed, `outline` when needed | | Make a diagram of this docker-compose file. | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | `importSource`, `validate` when needed, `outline` when needed | | Which roles can call which endpoints in this OpenAPI spec? | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | `importSource`, `validate` when needed, `outline` when needed | | Chart this CSV. | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | `importSource`, `validate` when needed, `outline` when needed | | Which icons should I use for S3, Lambda, Postgres and a queue on AWS? | [`map_icons`](https://docs.plotwork.dev/mcp/tools/map-icons.md) | `searchIcons` | | Pick icons for every service in this architecture. | [`map_icons`](https://docs.plotwork.dev/mcp/tools/map-icons.md) | `searchIcons` | | Replace the Mermaid diagrams in README.md with Plotwork diagrams. | [`sync_markdown`](https://docs.plotwork.dev/mcp/tools/sync-markdown.md) | `convertMermaidFences` when needed, `renderPair` | | Render the diagram fences in docs/login.md and give me the files to write. | [`sync_markdown`](https://docs.plotwork.dev/mcp/tools/sync-markdown.md) | `convertMermaidFences` when needed, `renderPair` | | Which diagram should I use to show who can call which endpoint? | [`suggest_diagram`](https://docs.plotwork.dev/mcp/tools/suggest-diagram.md) | `parse` when needed | | What is the best way to draw where our cloud spend goes? | [`suggest_diagram`](https://docs.plotwork.dev/mcp/tools/suggest-diagram.md) | `parse` when needed | | Suggest a diagram for comparing three message brokers. | [`suggest_diagram`](https://docs.plotwork.dev/mcp/tools/suggest-diagram.md) | `parse` when needed | | Which parts of this design doc need a diagram? | [`plan_document_diagrams`](https://docs.plotwork.dev/mcp/tools/plan-document-diagrams.md) | `parse` when needed | | Add pictures to this Confluence page. | [`plan_document_diagrams`](https://docs.plotwork.dev/mcp/tools/plan-document-diagrams.md) | `parse` when needed | | Plan the diagrams for our onboarding guide. | [`plan_document_diagrams`](https://docs.plotwork.dev/mcp/tools/plan-document-diagrams.md) | `parse` when needed | | Draw a three-tier network with load balancers. | [`find_pattern`](https://docs.plotwork.dev/mcp/tools/find-pattern.md) | `findPatterns` | | Which pattern shows a primary and a replica database in two availability zones? | [`find_pattern`](https://docs.plotwork.dev/mcp/tools/find-pattern.md) | `findPatterns` | | Find a serverless API pattern for AWS. | [`find_pattern`](https://docs.plotwork.dev/mcp/tools/find-pattern.md) | `findPatterns` | | Show me the three-tier network pattern for Azure and rename the office network to Branch office. | [`get_pattern`](https://docs.plotwork.dev/mcp/tools/get-pattern.md) | `fillPattern` | | What can I change in the two-zone network pattern? | [`get_pattern`](https://docs.plotwork.dev/mcp/tools/get-pattern.md) | `fillPattern` | | Draw a two-zone network on AWS with a replicated database. | [`draw_from_pattern`](https://docs.plotwork.dev/mcp/tools/draw-from-pattern.md) | `fillPattern`, `validate` when needed, `outline` when needed, `render` when needed | | Draw the web application pattern for Azure and call the API "Billing API". | [`draw_from_pattern`](https://docs.plotwork.dev/mcp/tools/draw-from-pattern.md) | `fillPattern`, `validate` when needed, `outline` when needed, `render` when needed | ## Recipes A recipe is a job and the calls that do it, in order. Each tool name links to its page. | Job | Calls | What you get | |---|---|---| | Draw a diagram from a request | [`get_diagram_syntax`](https://docs.plotwork.dev/mcp/tools/get-diagram-syntax.md), then [`draw_diagram`](https://docs.plotwork.dev/mcp/tools/draw-diagram.md) | Read the language once, write source, and call `draw_diagram` until it reports no errors. It returns an outline to check against the request and the SVG for light and dark. | | Choose the kind of diagram | [`suggest_diagram`](https://docs.plotwork.dev/mcp/tools/suggest-diagram.md), then [`get_diagram_syntax`](https://docs.plotwork.dev/mcp/tools/get-diagram-syntax.md), then [`draw_diagram`](https://docs.plotwork.dev/mcp/tools/draw-diagram.md) | `suggest_diagram` ranks the kinds for the request and any pasted data, each with a reason and a starter that parses. Read the chosen kind in full, edit the starter, and draw it. | | Add pictures to a long document | [`plan_document_diagrams`](https://docs.plotwork.dev/mcp/tools/plan-document-diagrams.md), then [`draw_diagram`](https://docs.plotwork.dev/mcp/tools/draw-diagram.md), then [`sync_markdown`](https://docs.plotwork.dev/mcp/tools/sync-markdown.md) | `plan_document_diagrams` lists the sections a picture would help, each with a starter drafted from its text. Check each with `draw_diagram`, put it under its heading as a diagram fence, then `sync_markdown` draws them all. | | Start from a common architecture | [`find_pattern`](https://docs.plotwork.dev/mcp/tools/find-pattern.md), then [`draw_from_pattern`](https://docs.plotwork.dev/mcp/tools/draw-from-pattern.md) | `find_pattern` ranks ready-made, checked diagrams for the request. `draw_from_pattern` fills one with a provider flavour and the names the request uses, and draws it. | | Choose icons for many services | [`map_icons`](https://docs.plotwork.dev/mcp/tools/map-icons.md), then [`draw_diagram`](https://docs.plotwork.dev/mcp/tools/draw-diagram.md) | `map_icons` picks an icon for each name and lists candidates for an ambiguous one. Put each chosen icon on its node, then draw. | | Move one Mermaid diagram to Plotwork | [`convert_mermaid`](https://docs.plotwork.dev/mcp/tools/convert-mermaid.md) | It returns Plotwork source, every diagnostic with its hint, and the drawing once the source is valid. Choose node types and icons, then check the result. | | Turn the diagrams of a Markdown file into pictures | [`sync_markdown`](https://docs.plotwork.dev/mcp/tools/sync-markdown.md) | It returns the rewritten document and the SVG files to write. Set `convertMermaid` to replace Mermaid fences first. | | Change a diagram that already exists as a hosted link or an SVG file | [`read_diagram`](https://docs.plotwork.dev/mcp/tools/read-diagram.md), then [`draw_diagram`](https://docs.plotwork.dev/mcp/tools/draw-diagram.md) | `read_diagram` returns the exact source from the link or the file. Edit it, call `draw_diagram` with `embedSource` true, then write the file again or publish again: a published diagram gets a new link, so replace the old one. | | Read and check a diagram someone else wrote | [`validate_diagram`](https://docs.plotwork.dev/mcp/tools/validate-diagram.md), then [`outline_diagram`](https://docs.plotwork.dev/mcp/tools/outline-diagram.md) | `validate_diagram` returns every error and warning. `outline_diagram` describes a valid diagram as plain text, so an agent can say what it shows. | ## What the operations mean A workflow is made of engine operations. Each is one small, checked step. | Operation | What it does | |---|---| | `validate` | Parse the source and lint it: every error, and the quality, icon, and group-type warnings. | | `outline` | Describe a valid diagram as plain text. | | `render` | Lay out and paint a valid diagram in one color scheme. | | `parse` | Parse the source and return its errors, without laying it out or linting it. | | `convertMermaid` | Convert one Mermaid diagram to Plotwork source. | | `importSource` | Read a file of another format (SQL, Prisma, Drizzle, OpenAPI, Terraform, a Terraform plan or state, Bicep, ARM, Pulumi, CloudFormation, CDK, Compose, Kubernetes, Helm, Kustomize, Backstage, Structurizr, AsyncAPI, GitHub Actions, GitLab CI/CD, Azure Pipelines, CSV, JSON, a tree, or a git log) into Plotwork source. | | `searchIcons` | Search the bundled icons for a name or an idea. | | `convertMermaidFences` | Replace every convertible mermaid fence of a Markdown document with a diagram fence. | | `renderPair` | Draw one diagram in the light and the dark theme with its text description. | | `findPatterns` | Search the pattern library for an architecture by its words, with optional provider, tag, and kind filters. | | `fillPattern` | Fill one pattern for one flavour with slot values: its source, slots, notes, group tree, and outline. | ## See also | Doc | Why | |---|---| | [Plotwork for AI agents](https://docs.plotwork.dev/agents/overview.md) | The tools and the ways to call them | | [Rules file for your agent](https://docs.plotwork.dev/agents/rules.md) | Guidance to paste into a project | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Every tool, with its arguments and result | | [Architecture patterns](https://docs.plotwork.dev/architecture-patterns.md) | The ready-made diagrams `find_pattern` ranks | --- # MCP server > Page: https://docs.plotwork.dev/mcp/overview. Index of every page: https://docs.plotwork.dev/llms.txt What the diagram MCP (Model Context Protocol) server is, which tools it exposes, and how it behaves. It is for anyone who wants an AI agent to write, check, and render diagrams. ## TL;DR | Question | Answer | |---|---| | What is it? | A server that gives an agent tools for diagrams. Workflow tools draw a diagram, convert Mermaid, choose icons, and sync a Markdown document, each in one call. Single tools read the source back out of an existing Plotwork SVG, read the language, validate, render, outline, and search icons. Every one of these only reads. The hosted server adds three tools for the diagrams you host: `publish_diagram` stores one, `list_diagrams` shows what is stored, and `delete_diagram` removes one. | | Which transports? | Standard input and output for a local process, and HTTP for the hosted server. | | Which diagrams can it draw? | Twenty kinds, chosen by the first word of the source: `flow`, `sequence`, `schema`, `git`, `gantt`, `class`, `state`, `timeline`, `journey`, `mindmap`, `treemap`, `pie`, `quadrant`, `layers`, `c4`, `matrix`, `sankey`, `xychart`, `tree`, and `swimlane`. Every tool takes any of them, and `get_diagram_syntax` returns the shared language with a line about each kind, or any one kind in full when it is given `kind`. See [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md). | | Does it change anything? | Not by default. Every tool of the local server is read-only and idempotent, and stores nothing. Every one but `read_diagram` is also closed-world; `read_diagram` fetches a link only when it is given one. The hosted server adds three tools that work on the diagrams you host. `publish_diagram` stores a diagram as a public link, or in an organisation's private library when you name an organisation, and only when you ask for one. `list_diagrams` only reads. `delete_diagram` removes a diagram for good, and only after you have said which one. The local server never stores or deletes anything. | | Does it need a key or a sign-in? | Not locally. The local server needs no key, no sign-in, and no account, and by default it makes no network requests. The one exception is `read_diagram` given the link of a Plotwork picture, which fetches that link and nothing else. If you choose, it can [count your use on your device, and share pseudonymous daily totals](https://docs.plotwork.dev/mcp/usage.md), and it does neither unless you turn it on. The hosted server asks for an API key or a browser sign-in, so each caller has an identity, a rate limit, and a daily allowance. | | What happens on a mistake in a diagram? | The tool returns every mistake with its line, column, and a hint. The agent fixes the source and calls again. | | Where does it run? | On your own machine, started by your client with `npx`, or hosted at `https://mcp.plotwork.dev`. Both give the same results. | ## 1. The loop an agent follows **Diagram source** ```diagram sequence "Agent and MCP server" desc="An agent reads the language once, then draws a diagram until it is valid. A valid diagram comes back with its warnings, outline, and pictures." actor agent agent "AI agent" actor mcp service "Diagram MCP server" sub="Read-only tools" agent -> mcp : "get_diagram_syntax" mcp --> agent : "Language, limits, examples" loop "until valid is true" { agent -> mcp : "draw_diagram" mcp --> agent : "Mistakes with line, column, hint, or warnings, outline, SVG" } ``` *Picture: Agent and MCP server: 2 participants. An agent reads the language once, then draws a diagram until it is valid. A valid diagram comes back with its warnings, outline, and pictures.* **Text description** **Participants:** - AI agent, agent - Diagram MCP server (Read-only tools), service **Steps:** - 1. AI agent to Diagram MCP server: get_diagram_syntax - 2. Diagram MCP server to AI agent (dashed): Language, limits, examples - loop: until valid is true - 3. AI agent to Diagram MCP server: draw_diagram - 4. Diagram MCP server to AI agent (dashed): Mistakes with line, column, hint, or warnings, outline, SVG ## 2. The tools | Kind | What it does | Examples | |---|---|---| | Workflow | Does the work of several single tools in one call, and returns problems in the source as data. Prefer it. | `draw_diagram` checks, describes, and draws a diagram. `convert_mermaid` converts one Mermaid diagram. `map_icons` chooses icons for many names and returns candidates instead of guessing. `sync_markdown` turns the diagram fences of a whole document into pictures. | | Single tool | Does one fine-grained step. | `read_diagram` gives back the source of an existing Plotwork SVG, from its text or a hosted link, so an agent can edit it; see [Editing an existing diagram](https://docs.plotwork.dev/editing-existing-diagrams.md). `get_diagram_syntax` returns the language as data: called with no arguments it gives the overview, and called with `kind` it gives that kind in full, so it is called once per session and once more for the kind being drawn. `validate_diagram` returns every mistake with a line, a column, and a hint. `render_diagram` returns one themed SVG. `outline_diagram` returns a plain-text description. `search_icons` finds icon names by name, tag, or idea. | | Hosted only | Stores, lists, or removes the diagrams you host, so only the hosted server offers them. An agent calls `publish_diagram` only when you ask to share a diagram, and `delete_diagram` only when you name the diagram to delete. | `publish_diagram` hosts a finished diagram at a public link and returns the link with a Markdown image to paste. With its `organization` option it files the diagram in an organisation's private library instead, and returns a sign-in page address and no public link. `list_diagrams` shows what you have hosted, in your own library or an organisation's: each diagram's id, name, folder, size, expiry, whether the owner locked it, and whether you may delete it. It changes nothing. `delete_diagram` deletes one diagram by its id, for good, and refuses a diagram the owner locked or one your role may not delete. | The [tool reference](https://docs.plotwork.dev/mcp/tools.md) lists every tool the local server has, with its arguments and results. `publish_diagram`, `list_diagrams` and `delete_diagram` are described in [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md), and their `organization` option in [Organisations](https://docs.plotwork.dev/organisations.md). ## 3. How it behaves | Behavior | Detail | |---|---| | Errors in a result | A diagram with mistakes comes back with its diagnostics, so the model can read them and fix its input. A workflow and `validate_diagram` return them as a normal result. `render_diagram` and `outline_diagram` mark the result as an error. Bad arguments are refused the same way. | | Crashes stay private | An unexpected failure is answered with a short generic message. No stack trace reaches the model. | | Privacy | Each call is logged as one line: tool, outcome, duration, and argument size. Argument contents are never logged. | | Nothing is stored | Each call stands alone. The server keeps nothing between calls and writes no files, except the local server with `PLOTWORK_OPEN=1`, which writes each drawing to your temporary folder to open it in your browser. The one exception is a diagram you publish with `publish_diagram` on the hosted server: Plotwork stores its picture so the link keeps working, or so your organisation's library has it, until you delete it with `delete_diagram` or in the portal. See [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md) and [Organisations](https://docs.plotwork.dev/organisations.md). | | Daily allowance | The hosted server counts each tool call toward one allowance per account, shared with the REST API and with every key and connected agent. The plan sets it: 300 a day on Free and 50,000 a day on Pro. Listing the tools and connecting are free. When it is spent, the server answers HTTP 429 and a JSON-RPC error with code -32029 and `usage.daily_limit` in its data, until 00:00 UTC. The local server has no allowance. See [Plans and billing](https://docs.plotwork.dev/plans-and-billing.md). | | Seeing a drawing | A valid answer of `draw_diagram`, `draw_from_pattern`, or `convert_mermaid` has `viewUrl`, a link to a page that draws the diagram in the browser from the part after `#`, which is never sent to a server. A client with MCP Apps (Claude, Claude Desktop, VS Code, ChatGPT) also shows the drawing inline. The local server reads `PLOTWORK_VIEWER_URL` to point the link elsewhere. See [Viewing diagrams](https://docs.plotwork.dev/viewing-diagrams.md). | | Judging the look | A valid answer of `validate_diagram`, `draw_diagram`, `draw_from_pattern`, or `convert_mermaid` has `quality`: a `score` from 0 to 100, a `grade` (`clean` at 85 or more, `fair` at 60 or more, `cluttered` below), the `measures` it comes from (line crossings, lines across boxes, overlapping labels, the smallest text in an 880px column, the shape, the empty space, how far lines detour, and for charts the series and slices), and up to three `hints` that name a statement to change, such as `add direction=down to the header line` or `shorten label "..."`. It is read from the laid-out picture, so a model that cannot see the drawing can still judge it, and it comes back with `svg` set to `never`. The same diagram always gets the same report. | | Icon aliases | An everyday word written as an icon name, such as `lucide:cart` or `lucide:bank`, draws the bundled icon it means and comes back in `notices` as I122 with the real name, never as an error. See [Icon names and lookup](https://docs.plotwork.dev/cloud-architecture/icon-names.md). | | Large drawings | Some clients refuse a tool answer over a size: Claude Code stops at 25,000 tokens. When the answer of `draw_diagram`, `draw_from_pattern`, or `convert_mermaid` would pass 36,000 characters (about 30 nodes in both color schemes), the pictures are left out from the dark one back, `picturesOmitted` says which, and the check, warnings, and outline still come back. `publish_diagram` gives a link instead on the hosted server. Pass `svg` as `always` to get every picture whatever the size, or `never` to skip drawing while you fix mistakes, which is cheaper. The local server reads `PLOTWORK_MCP_RESULT_LIMIT` (a number of characters, at least 4,000) to move the limit. The HTTP API ignores the limit. | | Limits | Source is limited to 50,000 characters. A flow diagram may have 500 nodes, and its connections may skip over at most 1000 tiers between them; past either the call returns error E140 with a hint, before any layout work, because a tangle of long connections costs far more than a tidy diagram of the same size. Quality warnings start at 15 flow nodes, 6 tables, or 6 actors. | ## 4. Connect to it There are three ways to connect, and each gives the same tools. The two hosted ways add `publish_diagram`, `list_diagrams` and `delete_diagram`. | Way | Address or command | What it needs | |---|---|---| | Local, free | `npx -y @buun_group/plotwork-mcp@0.1.0-beta.10` | Node 22 or newer. No key, no sign-in, no account. The client starts it as a child process. | | Hosted, with an API key | `https://mcp.plotwork.dev/mcp` | A key created at [app.plotwork.dev](https://app.plotwork.dev), sent in the `X-API-Key` header. | | Hosted, with a browser sign-in | `https://mcp.plotwork.dev/mcp-oauth` | A Plotwork account. The client opens a browser and you approve the connection. | Use the local server when you work on one machine. Use the hosted server when a client cannot start a local process, or when an agent runs on a server. [Connecting an agent](https://docs.plotwork.dev/mcp/connect.md) shows the settings for Claude Code, Cursor, VS Code, and Claude Desktop. ## 5. Save diagrams to your library Saving a finished diagram to Plotwork keeps it where your team can find it, beside the files and view links on your machine. Where it goes depends on how you connect. | You use | What happens | |---|---| | The hosted MCP server, and an organisation you belong to keeps members' diagrams in its library | The server tells the agent to save each finished diagram to that private library with `publish_diagram`, without asking you, because only members can open it. Each valid drawing's next step says so too, and the answer names the library and gives the page for members. See [Where members publish](https://docs.plotwork.dev/organisations.md#13-where-members-publish). | | The hosted MCP server with an organisation key | The same: the key's own library is private, so the agent saves there. | | The hosted MCP server with only your own library | The agent publishes only when you ask, because that link is public. | | The local MCP server | Nothing is saved: it has no account. Use the command line below. | | The command line | `plotwork publish file.diagram` saves every diagram of a file with the key in `PLOTWORK_API_KEY`, where your organisations decide, and prints the link. `--org` names the library. | | The Claude Code plugin | `PLOTWORK_PUBLISH=library` saves each file to the organisation in `PLOTWORK_ORG` after it is drawn. See [Claude Code plugin](https://docs.plotwork.dev/agents/claude-code-plugin.md#save-to-your-organisations-library). | ## See also | Doc | Why | |---|---| | [Plotwork for AI agents](https://docs.plotwork.dev/agents/overview.md) | What an agent needs to know in one page, and the rules to paste into a project | | [Connecting an agent](https://docs.plotwork.dev/mcp/connect.md) | Client settings and troubleshooting | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The validate and fix loop in practice | | [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md) | Publish a diagram with `publish_diagram`, see what is stored with `list_diagrams`, and remove one with `delete_diagram` | | [Organisations](https://docs.plotwork.dev/organisations.md) | Publish to, list, and delete from a team's private library with the `organization` option, and lock a diagram | | [Plans and billing](https://docs.plotwork.dev/plans-and-billing.md) | The daily allowance of each plan, and what happens when it is spent | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Arguments and results of every tool | | [API reference](https://docs.plotwork.dev/api/reference.md) | The same operations over plain HTTP | --- # Connecting an agent > Page: https://docs.plotwork.dev/mcp/connect. Index of every page: https://docs.plotwork.dev/llms.txt How to connect an AI agent or an editor to the diagram tools over MCP (Model Context Protocol). It is for anyone setting up Claude Code, Cursor, VS Code, Claude Desktop, or another MCP client. ## TL;DR | Way to connect | What you need | Use it when | |---|---|---| | Local, with `npx` | Node 22 or newer. No key, no sign-in, no account. | You work on one machine. This is the way to start. | | Hosted, with an API key | A key from [app.plotwork.dev](https://app.plotwork.dev), sent in the `X-API-Key` header. | A client cannot start a local process, or an agent runs on a server. | | Hosted, with a browser sign-in | A Plotwork account. Your client opens a browser. | You want no key to copy, store, or rotate. | All three give the same tools and the same results. Pick one per client. ## 1. Connect locally, without a key The local server is an npm package. Your client starts it with `npx`, it talks to the client over standard input and output, and by default it makes no network requests. The one exception is `read_diagram` given the link of a Plotwork picture, which fetches that link and nothing else. Counting your use and sharing totals with Plotwork are [options you turn on](https://docs.plotwork.dev/mcp/usage.md), and are off otherwise. It needs no key, no sign-in, and no account, because every tool is a pure computation over its arguments. ### Claude Code ```sh claude mcp add plotwork -- npx -y @buun_group/plotwork-mcp@0.1.0-beta.10 ``` Then run `/mcp` inside Claude Code to confirm that `plotwork` is connected and lists the Plotwork tools. To have every diagram Claude saves drawn to an SVG in your docs as well, install the [Claude Code plugin](https://docs.plotwork.dev/agents/claude-code-plugin.md) instead: it adds this server, a hook that runs the command line on save, and a preview on localhost. ### Cursor Add this to `~/.cursor/mcp.json`: ```json { "mcpServers": { "plotwork": { "command": "npx", "args": [ "-y", "@buun_group/plotwork-mcp@0.1.0-beta.10" ] } } } ``` ### VS Code Add this to `.vscode/mcp.json`: ```json { "servers": { "plotwork": { "type": "stdio", "command": "npx", "args": [ "-y", "@buun_group/plotwork-mcp@0.1.0-beta.10" ] } } } ``` ### Claude Desktop Add this to `claude_desktop_config.json`, then restart Claude Desktop: ```json { "mcpServers": { "plotwork": { "command": "npx", "args": [ "-y", "@buun_group/plotwork-mcp@0.1.0-beta.10" ] } } } ``` ### Other clients A client that starts an MCP server as a local process takes two parts: the command `npx` and the arguments `-y @buun_group/plotwork-mcp@0.1.0-beta.10`. The setting names differ between clients, so check your client's documentation for the exact keys. ### Open and self-hosted models For a model you run yourself (gpt-oss, Qwen, Gemma, or Llama on Ollama, LM Studio, vLLM, or llama.cpp) through Goose, Cline, Continue, LM Studio, or the OpenAI Agents SDK, set `PLOTWORK_TOOLS=core` so the server lists only its eight workflow tools, and give the model a context of 16,384 tokens or more. [Use Plotwork with open and self-hosted models](https://docs.plotwork.dev/mcp/open-models.md) has the settings for each client and the results of each model we ran. ## 2. Connect to the hosted server with an API key The hosted server is at `https://mcp.plotwork.dev/mcp`. Every request carries an API key in the `X-API-Key` header. The key gives your calls an identity, a rate limit per minute, and a share of your account's daily allowance: 300 calls on Free and 50,000 on Pro. Every tool call counts, from every key and every connected agent on the account. Listing the tools and connecting do not. [Plans and billing](https://docs.plotwork.dev/plans-and-billing.md) explains the plans and how to upgrade. The Usage page at [app.plotwork.dev/usage](https://app.plotwork.dev/usage) shows today's count and the last 30 days. A key can also belong to an [organisation](https://docs.plotwork.dev/organisations.md) instead of a person. An owner or admin makes it on the organisation's Keys tab. With that key, `publish_diagram` files a diagram in the organisation's private library by default, it cannot publish to any other organisation, and its calls count toward the owner's allowance. 1. Sign in at [app.plotwork.dev](https://app.plotwork.dev) and create a key. Copy it then, or press Reveal in your list of keys to see it again later. A key made before Reveal existed cannot be shown again. 2. Put it in an environment variable named `PLOTWORK_API_KEY`, so it never appears in a file you share. 3. Add the server to your client. ### Claude Code ```sh claude mcp add --transport http plotwork https://mcp.plotwork.dev/mcp \ --header "X-API-Key: $PLOTWORK_API_KEY" ``` ### Cursor Add this to `~/.cursor/mcp.json`: ```json { "mcpServers": { "plotwork": { "url": "https://mcp.plotwork.dev/mcp", "headers": { "X-API-Key": "${env:PLOTWORK_API_KEY}" } } } } ``` ### VS Code Add this to `.vscode/mcp.json`. VS Code asks for the key once and keeps it out of the file: ```json { "inputs": [ { "type": "promptString", "id": "plotwork-key", "description": "Plotwork API key", "password": true } ], "servers": { "plotwork": { "type": "http", "url": "https://mcp.plotwork.dev/mcp", "headers": { "X-API-Key": "${input:plotwork-key}" } } } } ``` ### Claude Desktop Claude Desktop starts local processes from its configuration file, so it reaches a hosted server through `mcp-remote`. Add this to `claude_desktop_config.json`, with your key in place of `your-api-key`, then restart Claude Desktop: ```json { "mcpServers": { "plotwork": { "command": "npx", "args": [ "mcp-remote", "https://mcp.plotwork.dev/mcp", "--header", "X-API-Key:${PLOTWORK_API_KEY}" ], "env": { "PLOTWORK_API_KEY": "your-api-key" } } } } ``` Write the header as `X-API-Key:${PLOTWORK_API_KEY}` with no space after the colon. Some clients split the argument on a space. ## 3. Connect to the hosted server with a browser sign-in The sign-in address is `https://mcp.plotwork.dev/mcp-oauth`. It is the same server, and your client signs you in through a browser (OAuth) instead of sending a key. You copy nothing, and you can disconnect a client from your account at any time. Add the address and nothing else. Your client sees that it must sign in and opens a browser, where you approve the connection. The approval page names your account, the name the client gave itself, and where it returns to. A client that identifies itself by a web address (a client ID metadata document) instead of registering also shows the website that published that address, under Published by. Each computer you connect a client from is its own connection: connecting on a second computer keeps the first connected, and connecting again on the same computer replaces that computer's connection. Every connection is listed under Connected agents in the portal, where you can end it. ### Claude Code ```sh claude mcp add --transport http plotwork https://mcp.plotwork.dev/mcp-oauth ``` Then run `/mcp` inside Claude Code and choose to authenticate `plotwork`. ### Cursor Add this to `~/.cursor/mcp.json`. Cursor marks the server as needing a sign-in, and you complete it from its MCP settings: ```json { "mcpServers": { "plotwork": { "url": "https://mcp.plotwork.dev/mcp-oauth" } } } ``` ### VS Code Add this to `.vscode/mcp.json`: ```json { "servers": { "plotwork": { "type": "http", "url": "https://mcp.plotwork.dev/mcp-oauth" } } } ``` ### Claude Desktop Open Settings, then Connectors, then add a custom connector, and enter this address: ```text https://mcp.plotwork.dev/mcp-oauth ``` ## 4. Check the connection by hand Each of these prints a JSON-RPC response when the server works. | Check | Command | |---|---| | Local version | `npx -y @buun_group/plotwork-mcp@0.1.0-beta.10 --version` | | Local handshake | `echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"check","version":"0"}}}' \| npx -y @buun_group/plotwork-mcp@0.1.0-beta.10` | | Hosted tool list | `curl -s -X POST https://mcp.plotwork.dev/mcp -H "X-API-Key: $PLOTWORK_API_KEY" -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'` | ## 5. What the server exposes | Item | Value | |---|---| | Server name | `plotwork` | | Tools | Every tool in the [tool reference](https://docs.plotwork.dev/mcp/tools.md), as `tools/list` returns them. The hosted server adds `publish_diagram`, which hosts a diagram at a public link, or in an organisation's private library when given the `organization` option, `list_diagrams`, which lists the diagrams you host, and `delete_diagram`, which deletes one of them: see [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md) and [Organisations](https://docs.plotwork.dev/organisations.md). The local server never has them. | | Behavior hints | Every tool is read-only, idempotent, and closed-world, except `publish_diagram` and `delete_diagram`. They are on the hosted server only. `publish_diagram` stores a diagram, so it is not read-only, and it only ever adds, so it is not destructive. It is idempotent and closed-world. `delete_diagram` removes a diagram, so it is destructive, and a second call finds nothing to delete, so it is not idempotent. It is closed-world. `list_diagrams` is read-only, idempotent and closed-world like the rest. | | Instructions | The server tells a model to read the syntax once, prefer the workflow tools, and call `draw_diagram` until the source is valid. A valid result carries the warnings, the outline, and the SVG. | | Failures | A diagram with mistakes is a normal tool result marked as an error, with line, column, and hint for each mistake. A crash returns a generic message; the detail goes to the log. | | Logs | One line per call: tool, outcome, duration, and argument size. Argument contents are never logged. The local server writes them to standard error. | ## 6. Troubleshooting | Symptom | Cause | Fix | |---|---|---| | The client cannot find the command | `npx` is not on the client's path | Use the absolute path to `npx`, or run `which npx` and use that. | | The client reports invalid JSON from the server | Something other than the server printed to standard output | Remove any wrapper script around the command, and start it as `npx -y @buun_group/plotwork-mcp@0.1.0-beta.10`. | | The local server does not start | Node is older than 22 | Run `node --version`, and install Node 22 or newer. | | The hosted server answers 401 | The key is missing, wrong, or revoked | Send it in the `X-API-Key` header, or create a new key at [app.plotwork.dev](https://app.plotwork.dev). | | The hosted server answers 429 | The key spent its allowance for the minute | Wait a minute and call again. | | The hosted server answers 429 with `usage.daily_limit` | The account made all of its calls for the day (300 on Free, 50,000 on Pro), counted across every key and connected agent | Wait until 00:00 UTC, when the count starts again, or upgrade to Pro. The `retry-after` header says how many seconds that is. See [Plans and billing](https://docs.plotwork.dev/plans-and-billing.md). | | `publish_diagram` answers with `hosting.quota_exceeded` | The plan holds no more hosted diagrams, or no more bytes of them | Delete a diagram on the Diagrams page of the portal, or ask the agent to list your diagrams and delete the one you name, or upgrade. An agent does not delete anything to make room on its own. See [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md). | | `publish_diagram` answers with `not_found` when it names an organisation | The organisation does not exist, or your account is not a member of it. The answer is the same for both. | Ask the person which organisation they mean, check its handle in the portal, and do not publish to another one or to the personal library instead. See [Organisations](https://docs.plotwork.dev/organisations.md). | | `publish_diagram` answers with `orgs.key_wrong_org` | The key belongs to one organisation and the call named another | Leave `organization` out to use the key's own organisation, or use a key for the one you want. | | `publish_diagram` answers with `orgs.folder_restricted` | The organisation's owner restricted that folder to some roles and people, and this account or key is not one of them. A key needs `key` named in the rule. | Ask the person which folder they may use, or ask the owner to change the rule. Do not publish somewhere else instead. See [Organisations](https://docs.plotwork.dev/organisations.md#6-restrict-a-folder). | | `publish_diagram` answers with `orgs.forbidden` | The account is a reader in the organisation, and a reader can look and download but not publish | Ask an admin or the owner to change the role, or publish with a member's account or an organisation key. | | `publish_diagram`, `list_diagrams` or `delete_diagram` is not in the tool list | You are connected to the local server, which does not have them | Connect to the hosted server with an API key or a browser sign-in. | | `delete_diagram` answers with `hosting.diagram_locked` | The owner locked the diagram, and nobody can rename, move or delete it until the owner unlocks it | Unlock it in the portal if it is yours to unlock, or ask the owner. The agent does not work around a lock. See [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md#lock-a-diagram) and [Organisations](https://docs.plotwork.dev/organisations.md#who-may-delete-and-lock). | | `delete_diagram` answers with `orgs.forbidden` | Your role in the organisation does not let you delete that diagram: a member deletes only their own, and a reader deletes none | Ask an admin or the owner to delete it, or to change your role. | | A sign-in does not finish | The browser did not return to the client | Start the sign-in again from the client, and finish it in the same browser. | | A tool call returns an error with diagnostics | The diagram source has mistakes | Fix each mistake using its line, column, and hint, then call again. | ## See also | Doc | Why | |---|---| | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The validate and fix loop the tools support | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Arguments and results of every tool | | [API reference](https://docs.plotwork.dev/api/reference.md) | The same operations over plain HTTP | | [MCP server](https://docs.plotwork.dev/mcp/overview.md) | What the server is and how it behaves | | [Open and self-hosted models](https://docs.plotwork.dev/mcp/open-models.md) | The core tool profile, client settings, and results for open models | | [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md) | Publish a diagram with the hosted server and share the link | | [Organisations](https://docs.plotwork.dev/organisations.md) | Publish to a team's private library, with a key of the organisation or your own | | [Plans and billing](https://docs.plotwork.dev/plans-and-billing.md) | The daily allowance of each plan and how to upgrade | --- # Writing diagrams with an agent > Page: https://docs.plotwork.dev/mcp/agent-loop. Index of every page: https://docs.plotwork.dev/llms.txt The loop an AI agent follows to produce a correct diagram: read the language once, write source, validate until clean, check the meaning, render. It is for people who connect an agent to the diagram tools, and for the prompt an agent reads. ## TL;DR | Rule | Yes | No | |---|---|---| | Learn the language | Call `get_diagram_syntax` once, before the first diagram, and again with `kind` for the kind you draw | Guess node types from memory | | Choose a kind | Unsure which kind fits? Call `suggest_diagram`; for a whole document, `plan_document_diagrams`. See [choosing a kind](#9-choosing-a-kind-and-planning-a-document) | Force every picture into a flow | | Check | Call `validate_diagram` after every edit, until `valid` is true | Render first and hope | | Fix | Use each error's `line`, `column`, and `hint` | Rewrite the whole diagram for one typo | | Icons | Call `search_icons` with the product name from the request, and use the first result | Invent an icon name, or guess `amazon:s3` | | Suggestions | Add the exact `icon=` or `type=` that `W204` or `W205` gives, or write `icon=none` | Ignore a suggestion without deciding | | Colors | Choose a `type` or a `tone` | Ask for hex colors; they do not exist in the language | | Meaning | Call `outline_diagram` and read it as the reader would | Assume the layout says what you meant | | Starting point | For a common architecture, call `find_pattern` and adapt the checked diagram it returns. See [architecture patterns](https://docs.plotwork.dev/architecture-patterns.md) | Draw a three-tier network or a serverless API from nothing | | Size | One idea per diagram, at most about 15 nodes, and one more for each group, up to 25 | One diagram for a whole system | | Fewer calls | Prefer a workflow: `draw_diagram` checks, describes, and draws in one call. See [workflows](#8-workflows-one-call-instead-of-several) | Call `validate_diagram`, `outline_diagram`, and `render_diagram` one after another | | Editing | To change a picture that already exists, call `read_diagram` with its link or its text, edit the source, and draw again with `embedSource` true. See [Editing an existing diagram](https://docs.plotwork.dev/editing-existing-diagrams.md) | Redraw it from the picture, or edit the SVG by hand | | Mermaid already | Have Mermaid already? See [Migrating from Mermaid](https://docs.plotwork.dev/migrating-from-mermaid.md) | Retype a Mermaid diagram by hand | ## 1. The loop **Diagram source** ```diagram sequence "Agent authoring loop" desc="An agent reads the language, writes a diagram, validates it until it is clean, then renders it." actor agent person "Agent" actor tools service "Diagram tools" agent -> tools : "get_diagram_syntax" tools --> agent : "Node types, arrows, limits, examples" loop "until valid is true" { agent -> tools : "validate_diagram" tools --> agent : "Errors with line, column, hint" } agent -> tools : "outline_diagram" tools --> agent : "Plain-text description" note over agent, tools : "The agent checks the outline against what it meant to draw" agent -> tools : "render_diagram" tools --> agent : "SVG and size" ``` *Picture: Agent authoring loop: 2 participants. An agent reads the language, writes a diagram, validates it until it is clean, then renders it.* **Text description** **Participants:** - Agent, person - Diagram tools, service **Steps:** - 1. Agent to Diagram tools: get_diagram_syntax - 2. Diagram tools to Agent (dashed): Node types, arrows, limits, examples - loop: until valid is true - 3. Agent to Diagram tools: validate_diagram - 4. Diagram tools to Agent (dashed): Errors with line, column, hint - 5. Agent to Diagram tools: outline_diagram - 6. Diagram tools to Agent (dashed): Plain-text description - Note over Agent and Diagram tools: The agent checks the outline against what it meant to draw - 7. Agent to Diagram tools: render_diagram - 8. Diagram tools to Agent (dashed): SVG and size ## 2. Read the language `get_diagram_syntax` returns the language as data, in two sizes so that an agent does not pay for kinds it does not draw. With no arguments it returns the overview: every node type with its default icon and tone, arrows, line options, tones, status icons, icon sets and providers, limits, how to edit an existing picture, one line about each kind, and the tested examples of the kinds those shared tables describe. Pass `kind` (for example `git` or `flow`) to get that kind in full: its statements, limits, and a tested example. `flow` also carries the group types with their frame, tone, and icon, the overlays, and a complete cloud architecture example. The error codes are in the [diagnostic codes](https://docs.plotwork.dev/reference/language/diagnostics.md) reference, because every diagnostic a tool returns already carries its code, line, column, and hint. It lists the icon sets but not every icon: it points to `search_icons`, which finds a product icon from its name. It never changes, so an agent reads the overview once per session, and a kind once. ## 3. Validate until clean `validate_diagram` takes the source and returns every problem in one pass. A mistake is a normal result, not an exception, so the agent can loop. Source with a misspelled type: ```plotwork flow "T" node a servce "A" ``` Each mistake has a code, a position, and a hint. Drawn with the offending line and a caret under the characters to change, it reads like this. ```text error E120: Unknown node type "servce" 2 | node a servce "A" | ^^^^^^ hint: Did you mean "service"? ``` The agent changes `servce` to `service`, validates again, and gets `valid: true`. The tool returns the same facts as data: | Field | Use | |---|---| | `valid` | `true` when the diagram can be rendered. Loop until it is. | | `errors` | Every mistake: `code`, `message`, `line`, `column`, `length`, and `hint`. | | `line`, `column`, `length` | The exact characters to change, counted inside the diagram source. The caret above is drawn from them. | | `hint` | The suggested fix. For an unknown word it names the closest valid one. | | `warnings` | Quality problems in a valid diagram: too dense, too wide, text cut, unconnected node. Fix them when the diagram is meant for a document. Two are suggestions, described below. | | `summary` and `size` | Present once the diagram is valid. | **The result as JSON** ```json { "valid": false, "errors": [ { "code": "E120", "message": "Unknown node type \"servce\"", "line": 2, "column": 8, "length": 6, "hint": "Did you mean \"service\"?" } ], "warnings": [] } ``` The full result also carries `kind` and `severity`, and `summary` and `size` as `null` until the diagram is valid. ### Icon and group-type suggestions For an architecture diagram, `validate_diagram` also suggests the icon and the group type an author left out. Each suggestion is a warning with the exact text to add, so the fix is one edit and never a guess. Nothing is applied for you. | Code | The warning says | The fix | |---|---|---| | `W204` | `Node "db" has no icon=, and its label names azure:cosmos-db` | Add `icon=azure:cosmos-db` to the node, or write `icon=none` to keep it plain. | | `W205` | `Group "net" has no type=, and its name says subnet` | Add `type=subnet` to the group, or `type=subnet provider=aws` when the name also names a provider. | The rules are fixed, so the same source gets the same suggestions every time. A label counts only as whole words, the longest phrase wins, and a label that fits two products or two types gets no suggestion. A generic phrase such as `SQL database` counts only inside a group, or a diagram, whose `provider=` owns that product, and a brand such as `Redis` counts only when it is the whole label. The [cloud architecture guide](https://docs.plotwork.dev/cloud-architecture.md) shows the full recipe. ## 4. Check the meaning A valid diagram can still say the wrong thing: an arrow reversed, a node in the wrong group. `outline_diagram` returns the diagram as plain text, in reading order. Compare it to the intent. The one-line summary becomes the alt text of the picture, and the rest appears in the collapsed Text description, so it is also what a screen reader hears. ## 5. Render and place `render_diagram` returns the SVG, its pixel size, and any warnings, in the `light` or `dark` theme. To put a diagram in a document, write it as a `diagram` fence in the Markdown file and call `sync_markdown` with the file; see [Diagrams in Markdown and on GitHub](https://docs.plotwork.dev/markdown-and-github.md). ## 6. Habits that produce good diagrams | Habit | Yes | No | |---|---|---| | Labels | `Auth API`, `Token exchange` | `The service that handles authentication requests` | | Detail | Put it in `sub="OAuth 2.1"` or a note | Put it in the label | | Types | `database` for a database, `gateway` for a gateway | `step` for everything | | Boundaries | A `group` with `type=trust-boundary` or `type=network`, a dashed frame | A text label that says "inside the VPC" | | Cloud products | `icon=aws:lambda` found with `search_icons`, one node per service | A generic box labeled `Lambda`, or one node per instance | | Outcomes | A `terminal` with `icon=status:success` or `status:denied` | A green node chosen by hand | | Splitting | Two diagrams joined by a sentence | Fifteen nodes plus a legend | ## 7. Ways to call the tools | Surface | How | |---|---| | MCP over stdio | `npx -y @buun_group/plotwork-mcp@0.1.0-beta.10`, started by your client, with no key. See [Connecting an agent](https://docs.plotwork.dev/mcp/connect.md). | | MCP over HTTP | `https://mcp.plotwork.dev/mcp` with an API key, or `https://mcp.plotwork.dev/mcp-oauth` with a browser sign-in. | | HTTP API | `POST /v1/diagrams/validate`, `/render`, `/outline`, `GET /v1/icons`, `GET /v1/vocabulary`, on the Pro plan. See the [API reference](https://docs.plotwork.dev/api/reference.md). | ## 8. Workflows: one call instead of several A workflow is a tool that performs several operations and returns one combined answer, so the agent spends one turn where it would spend three or four. The single tools stay for fine-grained steps. Every workflow is read-only, never guesses, and keeps its work bounded; the [tool reference](https://docs.plotwork.dev/mcp/tools.md) lists the operations and the limit of each. | Workflow | Ask your agent | It replaces | |---|---|---| | `draw_diagram` | "Draw this flow and check it" | `validate_diagram`, then `outline_diagram`, then `render_diagram`. Invalid source returns every error and draws nothing, so the loop is one call per fix. | | `convert_mermaid` | "Convert this Mermaid diagram to Plotwork" | The converter, then validation, outline, and render. The result lists everything that was dropped, with a hint for each. | | `map_icons` | "Choose icons for these services" | One `search_icons` call per name. A name that could mean more than one icon comes back with candidates and no guess. | | `find_pattern` | "Is there a ready-made diagram for a two-zone network?" | A guess at what exists. It ranks the checked patterns for the request in plain words and returns summaries, never source. | | `get_pattern` | "Show me the three-tier network pattern for Azure" | Reading a pattern by hand. It fills the pattern with your slot values and returns the source, every slot, the advice, and the diagram as a tree. | | `draw_from_pattern` | "Draw the serverless API pattern, with my service names" | `get_pattern`, then `validate_diagram`, then `render_diagram`. Slot values that break a rule come back as problems, and nothing is drawn. | | `sync_markdown` | "Replace the Mermaid diagrams in README.md" | Reading the fences, converting, rendering, and building the picture blocks for a whole document. | | `suggest_diagram` | "Which diagram shows who can call which endpoint?" | Reading every kind to choose one. It ranks the kinds for the request and gives a starter for each. | | `plan_document_diagrams` | "Which parts of this design doc need a picture?" | Reading a document section by section and choosing a kind for each. | To move the Mermaid diagrams in a document, the sequence is: 1. Call `sync_markdown` with the document text, its path, `convertMermaid: true`, and `mode: "check"`. Read the report. 2. Call it again with `mode: "sync"`. The result holds the rewritten document and the picture files. 3. Write the returned `markdown` to the document and each returned file to its `path`. The tool writes nothing itself. 4. Review the change in git before committing. [Migrating from Mermaid](https://docs.plotwork.dev/migrating-from-mermaid.md) explains the diagnostics and shows the result of each call. ## 9. Choosing a kind and planning a document Two workflows help before any source is written. Both are cheap: they read text, parse each starter once, and lay nothing out. | Workflow | Send | You get | |---|---|---| | `suggest_diagram` | `request` in plain words, and optional `data`: a pasted table, a list, `Label: value` lines, or numbers by period | Up to 3 kinds, best first, each with a one-sentence reason, a confidence, the statements to use, and a starter that parses. Names and numbers from the request and the data are in the starter, and `tailored` says so. | | `plan_document_diagrams` | `document`: Markdown, HTML, or text pasted from Confluence, Notion, or Google Docs | The sections a picture would help, in document order, each with its heading, its line, a kind, a reason, and a starter drafted from the section's lists, tables, numbers, and dates. Sections that read well as text, or already hold a diagram, are left out. | The shape of the text counts as much as its words: | The text holds | The kind suggested | |---|---| | A table of roles with R, A, C, and I | `matrix` with `style=raci` | | A table of roles with yes and no | `matrix`, a permission matrix | | Numbers by month, week, or quarter | `xychart` | | A number for each part | `pie`, `treemap`, or `sankey` | | `A -> B: 1200` lines | `sankey` | | Dated lines, such as `2024: Public API` | `timeline` | | `Role: action` lines where the roles come back | `swimlane` | | Paths such as `src/index.ts` | `tree` with `style=files` | | A numbered list | `flow` | A starter is a start: replace its placeholders, call `get_diagram_syntax` with its kind, and check it with `draw_diagram` before you draw. To put the pictures into the document, add each as a `diagram` fence under its heading and call `sync_markdown`. The MCP server also offers prompts, which a client lists in its prompt menu. Each tells the agent which tools to call, in order. | Prompt | Arguments | What it does | |---|---|---| | `diagram-from-description` | `description` | `suggest_diagram`, then `get_diagram_syntax`, then `draw_diagram`; a common architecture goes to `find_pattern` first. | | `diagrams-for-document` | `document`, optional `path` | `plan_document_diagrams`, then a checked diagram for each section, then `sync_markdown`. | | `permission-matrix` | `endpoints`, `roles` | A matrix with roles as columns and endpoints as rows. | | `architecture-overview` | `system` | `find_pattern` and `draw_from_pattern`, or a layered or C4 diagram written for the system. | | `convert-mermaid-file` | `markdown`, optional `path` | `sync_markdown` with `convertMermaid`, then `convert_mermaid` for each fence that did not convert. | ## 10. Save diagrams to your library Saving a finished diagram to Plotwork keeps it where your team can find it, beside the files and view links on your machine. Where it goes depends on how you connect. | You use | What happens | |---|---| | The hosted MCP server, and an organisation you belong to keeps members' diagrams in its library | The server tells the agent to save each finished diagram to that private library with `publish_diagram`, without asking you, because only members can open it. Each valid drawing's next step says so too, and the answer names the library and gives the page for members. See [Where members publish](https://docs.plotwork.dev/organisations.md#13-where-members-publish). | | The hosted MCP server with an organisation key | The same: the key's own library is private, so the agent saves there. | | The hosted MCP server with only your own library | The agent publishes only when you ask, because that link is public. | | The local MCP server | Nothing is saved: it has no account. Use the command line below. | | The command line | `plotwork publish file.diagram` saves every diagram of a file with the key in `PLOTWORK_API_KEY`, where your organisations decide, and prints the link. `--org` names the library. | | The Claude Code plugin | `PLOTWORK_PUBLISH=library` saves each file to the organisation in `PLOTWORK_ORG` after it is drawn. See [Claude Code plugin](https://docs.plotwork.dev/agents/claude-code-plugin.md#save-to-your-organisations-library). | ## See also | Doc | Why | |---|---| | [Connecting an agent](https://docs.plotwork.dev/mcp/connect.md) | Settings for Claude Code and other clients | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Arguments and results of every tool | | [API reference](https://docs.plotwork.dev/api/reference.md) | The same operations over HTTP | | [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md) | The language by example | | [Cloud architecture](https://docs.plotwork.dev/cloud-architecture.md) | The five-step recipe for AWS, Azure, and Google Cloud diagrams, with every icon and group type drawn | | [Architecture patterns](https://docs.plotwork.dev/architecture-patterns.md) | Ready-made, checked diagrams to start from, found and filled by tools | | [Editing an existing diagram](https://docs.plotwork.dev/editing-existing-diagrams.md) | Read the source back from a link or an SVG file, change it, and save or publish it again | | [Migrating from Mermaid](https://docs.plotwork.dev/migrating-from-mermaid.md) | Convert existing Mermaid diagrams, and what to do with each diagnostic | | [Language reference](https://docs.plotwork.dev/reference/language.md) | Every word the language accepts | --- # Local usage counts and sharing > Page: https://docs.plotwork.dev/mcp/usage. Index of every page: https://docs.plotwork.dev/llms.txt Count how you use the local MCP server on your own machine, and choose whether to share pseudonymous daily totals with Plotwork. Both are off by default. ## TL;DR | Question | Answer | |---|---| | Is anything counted or sent by default? | No. With nothing set, the local server reads no file but your [repository settings](https://docs.plotwork.dev/repository-settings.md) (`plotwork.config.json`, if you have one), writes no file, and makes no network request. The one exception is `read_diagram` given the link of a Plotwork picture, which fetches that link and sends nothing else. | | How do I turn it on? | Set the environment variable `PLOTWORK_USAGE` to `local` or `share`, in your agent client's `env` block or in your shell. | | What does `local` do? | It counts your tool calls in one file on your device. Nothing leaves the device. | | What does `share` do? | It counts as `local` does, and once a day sends totals for finished days to Plotwork. | | What is sent? | Counts by tool, diagram kind, and outcome, a random identifier that changes every month, the package version, your operating system family, and your Node major version. Never diagram text. | | How do I see what it holds? | Run `npx -y @buun_group/plotwork-mcp@0.1.0-beta.10 usage`. Run `usage preview` for the exact request. Neither sends anything. | | How do I stop and delete? | Unset the variable, then run `usage reset`. | ## 1. Turn it on The setting is an environment variable, so it lives where you can see it. There is no config file. | Value of `PLOTWORK_USAGE` | What happens | |---|---| | Unset, empty, or anything else | Off. The server behaves exactly as it always has. | | `local` | The server counts its tool calls in a file on your device and sends nothing. | | `share` | The server counts, and also sends totals to Plotwork once a day. | Only the exact lowercase words `local` and `share` turn anything on. Setting `DO_NOT_TRACK` to `1` or `true` stops the server from ever sending, whatever `PLOTWORK_USAGE` says, so `share` then behaves as `local`. Most agent clients start the server for you, so set the variable in the client's configuration: ```json { "mcpServers": { "plotwork": { "command": "npx", "args": [ "-y", "@buun_group/plotwork-mcp@0.1.0-beta.10" ], "env": { "PLOTWORK_USAGE": "local" } } } } ``` In a shell, run `PLOTWORK_USAGE=local npx -y @buun_group/plotwork-mcp@0.1.0-beta.10`. The setting is read once, when the server starts, so restart the server after you change it. ## 2. What is counted A count has three parts, and each comes from a short fixed list: | Part | Values | |---|---| | The tool | One of the tools the local server offers, such as `draw_diagram` or `validate_diagram`. | | The kind of diagram | One of `flow`, `sequence`, `schema`, `git`, `gantt`, `class`, `state`, `timeline`, `journey`, `mindmap`, `treemap`, `pie`, `quadrant`, `layers`, `c4`, `matrix`, `sankey`, `xychart`, `tree`, or `swimlane`, taken from the first word of the source, or `none` for a call with no source. | | How the call ended | `ok` (it produced its answer), `rejected` (your input made that impossible, such as source with errors), or `error` (the server failed). | The server adds one to the matching count for the day, in UTC. It never records the source, a tool's arguments or results, a name, a path, or an error message. The counts live in `usage.json` in a `.plotwork` folder in your home folder. Set `PLOTWORK_HOME` to use another folder. The file is readable only by you where your system allows it, holds the last 90 days, and also holds a random secret that never leaves your device. Counting never slows or fails a tool call. ## 3. What sharing sends In `share` mode the server sends once a day, about 5 seconds after it starts and then every 24 hours, to `https://api.plotwork.dev/telemetry/usage`. It sends only days that are over and not yet sent, never today. A failure is silent, and it tries again the next day. ```json { "schema": 1, "client": { "name": "plotwork-mcp", "version": "1.4.2", "os": "linux", "node": 22 }, "days": [ { "day": "2026-10-08", "id": "3f9a1c0e5b7d42a8e6c1f0d9b2a47e65", "counts": [ { "tool": "draw_diagram", "kind": "flow", "outcome": "ok", "count": 12 } ] } ] } ``` | Field | What it is | |---|---| | `id` | A random identifier made on your device from the secret in the file and the month. It changes every month, so Plotwork can count how many installs were active in a month but cannot follow one install from month to month. The secret is never sent. | | `client` | The package version, your operating system family (`linux`, `darwin`, `win32`, or `other`), and your Node major version. | | `counts` | The counts from section 2, for that day. | The request carries no cookie, account, or key. The package chooses only a content type and a user agent that names the package and its version. It never follows a redirect, and it gives up after 3 seconds. Never sent: diagram text, tool arguments or results, names, folders, file paths, error messages, your user name, your host name, environment variables, and the time of day. On Plotwork's side, the totals go into a table of their own that is never used for limits, billing, or anything about an account. Plotwork stores no address, header, or cookie with them. The route is rate limited, and Cloudflare's rate-limiting service holds your address as a counter key for 60 seconds. Rows are deleted after 400 days. The [privacy policy](https://docs.plotwork.dev/privacy-policy.md#usage-totals) says the same in full. ## 4. See what it holds These commands are for you. They never send anything. | Command | What it shows | |---|---| | `npx -y @buun_group/plotwork-mcp@0.1.0-beta.10 usage` | The mode and which setting chose it, the file and whether it exists, your counts for the last 30 days, your current identifier, and when the next send is due. | | `npx -y @buun_group/plotwork-mcp@0.1.0-beta.10 usage preview` | The exact request the next send would post, or a note that there is nothing to send. It works in any mode. | | `npx -y @buun_group/plotwork-mcp@0.1.0-beta.10 usage reset` | Deletes the file, which removes your secret and every count. | ## 5. Stop and delete 1. Remove `PLOTWORK_USAGE` from your client's configuration, or set it to `off`, and restart the server. 2. Run `npx -y @buun_group/plotwork-mcp@0.1.0-beta.10 usage reset` to delete the file on your device. 3. To have totals already sent deleted, run `usage` to read your current identifier and write to legal@buungroup.com with it. Totals sent under an earlier month's identifier cannot be tied to you, and are deleted as they age out. ## See also | Doc | Why | |---|---| | [MCP server](https://docs.plotwork.dev/mcp/overview.md) | What the local and hosted servers are and how they differ | | [Connecting an agent](https://docs.plotwork.dev/mcp/connect.md) | Starting the local server from an agent client | | [Privacy policy](https://docs.plotwork.dev/privacy-policy.md) | The full statement of what Plotwork collects, including usage totals | --- # Self-hosting the engine > Page: https://docs.plotwork.dev/mcp/self-hosting. Index of every page: https://docs.plotwork.dev/llms.txt Run the Plotwork engine inside your own network as one container: the MCP tools for your agents and the REST drawing routes for your services, with no account and no request leaving your network. It is for platform and security teams at companies that cannot send diagram text to a hosted service. ## TL;DR | Question | Answer | |---|---| | What is it? | One container image with the diagram engine, MCP over Streamable HTTP at `/mcp`, the REST drawing routes under `/v1`, and `/healthz`. | | Does anything leave my network? | No. The server makes no outbound request, sends no telemetry, and stores nothing. Logs go to standard output and never hold diagram text. | | What is not in it? | Accounts, the portal, hosted diagram links, organisations, brand themes, and icon packs. Those are features of the hosted service. | | How do I run it? | Put the image in your registry, set `PLOTWORK_TOKEN`, and start it with Docker, Compose, or Kubernetes. | | How does an agent connect? | Point the client at `http://<host>:3106/mcp` with the header `Authorization: Bearer <token>`. | | Is it the same engine? | Yes. The tools, their schemas, and the pictures are the same as the hosted service and the npm package. | ## What is included | Part | Self-hosted | Hosted service | |---|---|---| | Every MCP tool of the local server (draw, edit, convert, import, map icons, sync Markdown, patterns, read, compare, validate, render, outline, search icons) | Yes | Yes | | REST: validate, render, draw, edit, convert Mermaid, sync Markdown, find, get, and draw a pattern | Yes, same paths and bodies | Yes | | `read_diagram` and `compare_diagrams` with a link to a hosted picture | No: send the SVG text or the source | Yes | | A `viewUrl` on a drawing | No: it points at Plotwork's viewer | Yes | | Publishing, listing, and deleting hosted diagrams | No | Yes | | Accounts, API keys, the portal, organisations | No | Yes | | Brand themes and icon packs | No | Yes, on plans that include them | | Usage counting of any kind | No | Anonymous daily totals | The REST routes take the same request bodies and answer the same shapes as the [hosted API](https://docs.plotwork.dev/api/reference.md), so a client written for one works with the other after a change of base address and credential. The self-hosted server adds two answers the hosted API does not have: `503 engine.busy` when every worker is busy and the queue is full, and `504 engine.timeout` when a job runs past its deadline. ## 1. Get the image Plotwork gives your organisation the image. Push it into your own registry, so your hosts pull it from inside your network. The examples on this page call it `plotwork-selfhost`. | Property | Value | |---|---| | Base | Distroless Node 24, pinned by digest | | Size | About 56 MB compressed | | User | 65532, not root | | Filesystem | Runs with a read-only root filesystem; writes nothing | | Port | 3106 | | Health check | Built in: `GET /healthz` over loopback | ## 2. Run it Make a token and keep it in your secret store: ```sh openssl rand -hex 32 ``` Run the container with the token in its environment: ```sh docker run -d --name plotwork \ --read-only --cap-drop ALL --security-opt no-new-privileges \ -p 127.0.0.1:3106:3106 \ -e PLOTWORK_TOKEN \ plotwork-selfhost ``` Check it: ```sh curl http://127.0.0.1:3106/healthz ``` ### With Compose This file runs the server on an internal network, which has no route out. Docker publishes no port from an internal network, so the services that call Plotwork, such as an agent gateway or a reverse proxy, join the `plotwork` network and reach it at `http://plotwork:3106`. Put `PLOTWORK_TOKEN` in a `.env` file beside it, never in the file itself. ```yaml services: plotwork: image: plotwork-selfhost restart: unless-stopped environment: PLOTWORK_TOKEN: ${PLOTWORK_TOKEN:?Set PLOTWORK_TOKEN in .env} PLOTWORK_WORKERS: '2' networks: - plotwork read_only: true cap_drop: - ALL security_opt: - no-new-privileges:true mem_limit: 1g pids_limit: 64 networks: plotwork: internal: true ``` ### Settings Every setting is an environment variable. A value the server cannot use stops it at start with a message that names the variable. | Variable | Default | Meaning | |---|---|---| | `PLOTWORK_TOKEN` | none | The bearer token for `/mcp` and `/v1`, at least 32 characters. | | `PLOTWORK_HOST` | `0.0.0.0` in the image | The address to listen on. | | `PLOTWORK_PORT` | `3106` | The port to listen on. | | `PLOTWORK_ALLOW_OPEN` | unset | `1` serves without a token on an address other than loopback. Use it only where the network itself is the control. | | `PLOTWORK_ALLOWED_ORIGINS` | none | Browser origins allowed to call, separated by commas. | | `PLOTWORK_WORKERS` | 2 | Worker threads that run the engine. One layout uses one thread for up to a second. | | `PLOTWORK_QUEUE` | `32` | Jobs that may wait for a worker before more are refused. | | `PLOTWORK_TIMEOUT_MS` | `30000` | The deadline of one job, waiting included. | | `PLOTWORK_WORKER_MEMORY_MB` | `512` | The heap limit of each worker. | | `PLOTWORK_MAX_BODY_BYTES` | `4194304` | The largest request body. | | `PLOTWORK_MCP_RESULT_LIMIT` | `36000` | Characters of an MCP drawing answer before its pictures are left out, so an AI client can read it. | | `PLOTWORK_TOOLS` | `full` | `core` lists only the eight workflow tools on `/mcp`, with short descriptions, for an open model with a small context. See [Use Plotwork with open and self-hosted models](https://docs.plotwork.dev/mcp/open-models.md). Any other value stops the start. | | `PLOTWORK_LICENSE` | none | Your licence. `/healthz` shows whether one is set and a short fingerprint of it, never the value. | Without `PLOTWORK_TOKEN`, the server listens on loopback only. The image listens on every address, so it does not start without a token unless `PLOTWORK_ALLOW_OPEN=1` is set. ## 3. Connect an agent Every client below sends the token as a bearer header. Replace `plotwork.internal` with the address of your server. ### Claude Code ```sh claude mcp add --transport http plotwork http://plotwork.internal:3106/mcp \ --header "Authorization: Bearer $PLOTWORK_TOKEN" ``` ### Cursor Add this to `~/.cursor/mcp.json`: ```json { "mcpServers": { "plotwork": { "url": "http://plotwork.internal:3106/mcp", "headers": { "Authorization": "Bearer ${env:PLOTWORK_TOKEN}" } } } } ``` ### VS Code Add this to `.vscode/mcp.json`. VS Code asks for the token once and keeps it out of the file: ```json { "inputs": [ { "type": "promptString", "id": "plotwork-token", "description": "Plotwork token", "password": true } ], "servers": { "plotwork": { "type": "http", "url": "http://plotwork.internal:3106/mcp", "headers": { "Authorization": "Bearer ${input:plotwork-token}" } } } } ``` ### A service over REST ```sh curl -X POST http://plotwork.internal:3106/v1/diagrams/render \ -H "Authorization: Bearer $PLOTWORK_TOKEN" \ -H "content-type: application/json" \ -d '{"source": "flow \"Checkout\"\nnode web service \"Web\"\nnode db database \"Orders\"\nweb -> db"}' ``` ## 4. Security notes | Topic | What the server does | What you do | |---|---|---| | Transport | Plain HTTP. | Put TLS in front: a reverse proxy, a service mesh, or an ingress. | | Authentication | One static bearer token, compared in constant time. A missing or wrong token is a 401. | Keep the token in a secret store, give each environment its own, and rotate it by restarting with a new one. | | Outbound network | Makes no request. `fetch` and the socket clients are switched off in the process, and a link sent to a tool is refused. | Block egress anyway, with an internal network, a network policy, or a firewall rule, so the container has no route out. | | Browsers | Refuses a request whose `Origin` is not in `PLOTWORK_ALLOWED_ORIGINS`, which also stops a web page from reaching a server on loopback. | List only the origins of your own tools. | | Limits | A body cap, a bounded queue, a deadline on every job, and a heap cap on each worker. A job past its deadline is stopped and its worker replaced. | Set container memory and CPU limits; about 250 MB at rest with two workers. | | Logs | One JSON line per request and per tool call: path, status, tool, outcome, duration, and argument size. Never diagram text, a header value, or an error message. | Collect standard output with your log pipeline. | | Storage | None. Every request is worked on in memory and forgotten. | Run with a read-only root filesystem. | | Telemetry | None. `PLOTWORK_USAGE`, which turns on counting in the npm package, does nothing here. | Nothing. | ## See also | Doc | Why | |---|---| | [MCP server](https://docs.plotwork.dev/mcp/overview.md) | The tools, and how the local and hosted servers differ | | [Connecting an agent](https://docs.plotwork.dev/mcp/connect.md) | Client settings for the hosted server and the npm package | | [API reference](https://docs.plotwork.dev/api/reference.md) | Request and response shapes of the REST routes | | [Privacy policy](https://docs.plotwork.dev/privacy-policy.md) | What the hosted service collects, none of which applies to a self-hosted server | --- # Use Plotwork with open and self-hosted models > Page: https://docs.plotwork.dev/mcp/open-models. Index of every page: https://docs.plotwork.dev/llms.txt How to run Plotwork's MCP tools with an open model, such as gpt-oss, Qwen, Gemma, or Llama, served by Ollama, LM Studio, vLLM, or llama.cpp, through an MCP client such as Goose, Cline, Continue, or the OpenAI Agents SDK. It is for anyone who wants diagrams drawn by a model that runs on their own hardware. ## TL;DR | Question | Answer | |---|---| | Does Plotwork work with open models? | Yes. The MCP server is the same for every model. The model needs tool calling and enough context. | | What should I change first? | The context. Ollama runs most models at 4,096 tokens on a GPU with less than 24 GB, which is too small for any agent. Set 16,384 or more. | | Which tools should I list? | The `core` profile: set `PLOTWORK_TOOLS=core`. It lists the eight workflow tools in about 1,700 tokens instead of about 17,500 for all eighteen. | | Which model did best? | On 2026-10-20, gpt-oss-20b passed all four of our tasks with the core profile and gemma4:12b passed three, at a 16,384-token context. See [Results](#results). | | Can I run it with no network at all? | Yes. Run the model locally and Plotwork from the npm package, or from the [self-hosted image](https://docs.plotwork.dev/mcp/self-hosting.md), which makes no outbound request. | | Which hardware? | gpt-oss-20b runs on a 16 GB GPU. gpt-oss-120b needs about 80 GB of GPU memory, so serve it with vLLM on a data-center GPU or use a cloud provider. | ## 1. Serve the model Every server below speaks the OpenAI Chat Completions API, which every MCP client in this guide can call. Pick a model that supports tool calling. | Server | Endpoint | Context | |---|---|---| | Ollama | `http://localhost:11434/v1` | 4,096 tokens by default on a GPU with less than 24 GB of memory (32,768 from 24 GB, 256k from 48 GB). The `/v1` endpoint cannot change it per request, so set it on the server or in a Modelfile, below. | | LM Studio 0.3.17 or later | `http://localhost:1234/v1` | Set the context length when you load the model. | | vLLM | `http://<host>:8000/v1` | The model's own, limited by GPU memory. | | llama.cpp `llama-server` | `http://localhost:<port>/v1` | `-c 32768`. Pass `--port`, because the default changed between releases. | Raise Ollama's context for every model: ```sh OLLAMA_CONTEXT_LENGTH=32768 ollama serve ``` Or make a model with a larger context and call it by name: ```text FROM gpt-oss:20b PARAMETER num_ctx 32768 ``` ```sh ollama create gpt-oss-32k -f Modelfile ollama ps # the CONTEXT column shows what a loaded model runs with ``` Serve gpt-oss with tool calling on vLLM: ```sh vllm serve openai/gpt-oss-20b --tool-call-parser openai --enable-auto-tool-choice # gpt-oss-120b on one 80 GB GPU vllm serve openai/gpt-oss-120b --tool-call-parser openai --enable-auto-tool-choice --gpu-memory-utilization 0.95 --max-num-batched-tokens 1024 ``` Serve a GGUF model with tool calling on llama.cpp: ```sh llama-server --jinja -hf bartowski/Qwen2.5-7B-Instruct-GGUF:Q4_K_M -c 32768 --port 8080 ``` ## 2. Choose the core profile The server lists its tools to the client on every connection, and the client hands the list to the model before any work starts. All eighteen tools, with their descriptions and schemas, are about 36,600 characters as the model sees them (about 9,100 tokens), and the server instructions add about 5,100. A model with a 16,384-token context then has a few thousand tokens left for the task, the language reference, and the answers. The `core` profile lists eight tools with short descriptions and short input schemas, about 6,900 characters with its instructions (about 1,700 tokens): | Tool | What it does | |---|---| | `suggest_diagram` | Ranks the kinds of diagram for a request and gives a starter that parses. | | `get_diagram_syntax` | Returns the language of one kind, with a tested example. | | `draw_diagram` | Checks the source and draws it, with every error's line, column, and hint. | | `edit_diagram` | Changes a diagram with typed operations. | | `import_source` | Builds a diagram from SQL, OpenAPI, Terraform, docker-compose, Kubernetes, and more. | | `convert_mermaid` | Converts one Mermaid diagram. | | `sync_markdown` | Draws every diagram fence of a Markdown document. | | `map_icons` | Finds the icons for many service names at once. | The tools answer exactly as they do in the full profile; only what the list says about them changes. The pattern tools, `plan_document_diagrams`, and the single tools are left out, because one of these eight covers each of them. | Set it | How | |---|---| | npm package | The environment variable `PLOTWORK_TOOLS=core`, or the argument `--tools core`. | | Self-hosted image | `PLOTWORK_TOOLS=core` on the container. Any other value stops the start. | Use `core` for a model with a context of 32,768 tokens or less, or one that picks the wrong tool from a long list. Use the full profile, the default, for a model with a large context that you want to use the patterns and single tools. `PLOTWORK_TOOLS` and `--tools` need a release of `@buun_group/plotwork-mcp` published after 2026-10-20. An older release ignores the variable and lists every tool, and refuses `--tools`, so the examples below set the variable. ## 3. Connect a client Each snippet starts the npm package with `npx`. Status says whether we ran it ourselves on 2026-10-20 or checked it against the client's documentation only. ### Goose `~/.config/goose/config.yaml`: ```yaml GOOSE_PROVIDER: ollama GOOSE_MODEL: gpt-oss:20b extensions: plotwork: type: stdio name: plotwork enabled: true cmd: npx args: ["-y", "@buun_group/plotwork-mcp@0.1.0-beta.10"] envs: PLOTWORK_TOOLS: core timeout: 300 ``` Set `OLLAMA_HOST` when Ollama runs on another machine. Status: checked against the [Goose configuration docs](https://goose-docs.ai/docs/guides/config-files), not run. ### Cline and Roo Code Open MCP Servers, then Configure MCP Servers, and add the server. Roo Code reads the same shape from `.roo/mcp.json` in a project, with `alwaysAllow` in place of `autoApprove`. ```json { "mcpServers": { "plotwork": { "command": "npx", "args": [ "-y", "@buun_group/plotwork-mcp@0.1.0-beta.10" ], "env": { "PLOTWORK_TOOLS": "core" }, "disabled": false, "autoApprove": [] } } } ``` In the settings, choose the API provider Ollama (`http://localhost:11434`) or LM Studio (`http://localhost:1234`), then the model. Status: checked against the [Cline MCP docs](https://docs.cline.bot/mcp/configuring-mcp-servers), not run. ### Continue `config.yaml`. Continue runs MCP tools in agent mode only. ```yaml name: Local version: 0.0.1 schema: v1 models: - name: gpt-oss provider: ollama model: gpt-oss:20b capabilities: - tool_use mcpServers: - name: plotwork type: stdio command: npx args: ["-y", "@buun_group/plotwork-mcp@0.1.0-beta.10"] env: PLOTWORK_TOOLS: core ``` Status: checked against the [Continue MCP docs](https://docs.continue.dev/customize/deep-dives/mcp), not run. ### LM Studio In the Program tab, choose Install, then Edit mcp.json: ```json { "mcpServers": { "plotwork": { "command": "npx", "args": [ "-y", "@buun_group/plotwork-mcp@0.1.0-beta.10" ], "env": { "PLOTWORK_TOOLS": "core" } } } } ``` Status: LM Studio's [MCP docs](https://lmstudio.ai/docs/app/mcp) say the file follows Cursor's `mcp.json`; not run. ### OpenAI Agents SDK Python, with the model on Ollama and tracing off, so nothing is sent to OpenAI: ```python import asyncio from agents import Agent, AsyncOpenAI, OpenAIChatCompletionsModel, Runner, set_tracing_disabled from agents.mcp import MCPServerStdio set_tracing_disabled(True) model = OpenAIChatCompletionsModel(model="gpt-oss:20b", openai_client=AsyncOpenAI(base_url="http://localhost:11434/v1", api_key="ollama")) async def main(): async with MCPServerStdio( name="plotwork", params={"command": "npx", "args": ["-y", "@buun_group/plotwork-mcp@0.1.0-beta.10"], "env": {"PLOTWORK_TOOLS": "core"}}, ) as server: agent = Agent(name="Diagrams", instructions="Draw diagrams with Plotwork.", model=model, mcp_servers=[server]) print((await Runner.run(agent, "Draw the sign-in sequence of a web app.")).final_output) asyncio.run(main()) ``` Status: checked against the [Agents SDK docs](https://openai.github.io/openai-agents-python/mcp/), not run. ### Codex CLI `~/.codex/config.toml`: ```toml oss_provider = "ollama" [mcp_servers.plotwork] command = "npx" args = ["-y", "@buun_group/plotwork-mcp@0.1.0-beta.10"] env = { PLOTWORK_TOOLS = "core" } startup_timeout_sec = 30 ``` ```sh codex exec --oss --local-provider ollama -m gpt-oss:20b "Draw the sign-in sequence of a web app and save it to signin.diagram" ``` Status: run on 2026-10-20 with Codex CLI 0.154.0 and Ollama 0.30.6, and it did not work. Codex started the server and listed its tools, but it sends a server's tools to the model as one group, and Ollama passed that group to gpt-oss as a single function named `plotwork` with no parameters, so the model could call no Plotwork tool. The run also needs a context above Ollama's default: at 4,096 tokens, Codex's own prompt fills it. Use another client until Codex and Ollama agree on grouped tools. ### A client of your own An MCP client that bridges to an OpenAI-compatible model turns each listed tool into a function: the tool's name, its description, and its input schema as `parameters`. Every Plotwork input schema is an object with plain types, flat nullable unions, and short enums, with no `$ref`, `oneOf`, or `allOf`, which Ollama, llama.cpp, vLLM, and LM Studio all read. OpenAI's strict mode needs every object closed, so the tools that take an open map of keys (`settings`, a pattern's `slots`, an edit's `options`) must be sent with `strict: false`; in the core profile that is `edit_diagram` alone. ## 4. Run it fully offline The npm package makes no network request by default, so a local model and `npx` already keep diagram text on your machine once the package is installed. For a team, run the [self-hosted image](https://docs.plotwork.dev/mcp/self-hosting.md) next to the model server: one container with MCP over Streamable HTTP at `/mcp`, no outbound request, and no telemetry. Start it with `PLOTWORK_TOOLS=core` and point a client that speaks Streamable HTTP at `http://<host>:3106/mcp` with `Authorization: Bearer <token>`. ## Results We ran each model on four tasks through the stdio server on 2026-10-20: a sign-in sequence diagram saved to a file, a Mermaid flowchart converted, an AWS architecture with real AWS icons, and a Terraform snippet imported with `import_source`. A task passes when the saved file is valid, of the right kind, and meets the task's own check. Each cell is one run at temperature 0 with at most twelve model replies, on Ollama 0.30.6 and an RTX 5080 with 16 GB, at a 16,384-token context. Four tasks is a small sample: one run in either direction moves a model by a quarter, so read these as a guide, not a ranking. | Model | Size on disk | Core profile | Full profile | Notes | |---|---|---|---|---| | gpt-oss:20b | 13 GB | 4 of 4 | 2 of 4 | Followed the hints to a valid diagram. With the full profile it once sent a tool call Ollama could not parse, and once saved source it had not checked. | | gemma4:12b | 7.6 GB | 3 of 4 | 2 of 4 | Dropped a quote when it copied an imported diagram into the file. With the full profile it gave up on two tasks after repeated errors, without saving a file. | | qwen3.5:9b | 6.6 GB | 3 of 4 | 1 of 4 | Escaped line breaks twice (`\\n`) in the AWS task until it ran out of turns. | | qwen2.5-coder:14b | 9.0 GB | 0 of 4 | 0 of 4 | Writes tool calls as JSON in a code block, which Ollama does not read as a call. Use qwen2.5 or qwen3 instead. | | llama3 (8B) | 4.7 GB | 0 of 4 | 0 of 4 | Ollama refuses tools for this model. | | llama3.2:3b | 2.0 GB | 0 of 4 | 0 of 4 | Calls tools, but too often with the wrong arguments or as text. | At Ollama's default 4,096-token context, gpt-oss:20b and gemma4:12b passed no task with either profile: the full tool list alone is longer than the context, so the model saw a cut prompt and made no call, and with the core profile the first answer from `get_diagram_syntax` filled what was left. At 32,768 tokens gpt-oss:20b passed 3 of 4 with the core profile and 2 of 4 with the full one. What the runs changed: the parse errors these models made most now say what to write, each in one error (a Mermaid arrow such as `-->>`, a quote or line break escaped twice, a node type used as the statement, a second header line, a `title` line, an option on a line of its own, a group or actor with no label), and the core profile tells the model to save only source a tool has called valid. On the same tasks, the core profile went from 7 to 10 passes of 16 for the four models that call tools (gpt-oss, gemma, qwen3.5, and llama3.2, which passed none either time), and the full profile stayed at 5 of 16. ## See also | Doc | Why | |---|---| | [Connecting an agent](https://docs.plotwork.dev/mcp/connect.md) | Client settings for Claude Code, Cursor, VS Code, and Claude Desktop | | [MCP server](https://docs.plotwork.dev/mcp/overview.md) | Every tool, and how the local and hosted servers differ | | [Self-hosting the engine](https://docs.plotwork.dev/mcp/self-hosting.md) | The container for running Plotwork inside your network | | [Writing with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop a model follows to write, check, and draw a diagram | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The full schema of every tool | --- # Tool reference > Page: https://docs.plotwork.dev/mcp/tools. Index of every page: https://docs.plotwork.dev/llms.txt The operations an AI agent can call. Each tool has a name, a description written for a model, typed arguments, and a typed result, and its own page. The same manifest backs the HTTP API and the MCP (Model Context Protocol) server, so a tool behaves the same everywhere. Workflows come first: each does the work of several single tools and returns one combined answer. ## TL;DR | Question | Answer | |---|---| | Which tool do I call first? | [`get_diagram_syntax`](https://docs.plotwork.dev/mcp/tools/get-diagram-syntax.md), once, to read the language. | | What is a workflow? | A tool that performs several engine operations and returns one combined answer, so an agent spends one turn where it would spend several. It publishes its steps and the most operations one call can perform. | | What is the write loop? | Write source and call [`draw_diagram`](https://docs.plotwork.dev/mcp/tools/draw-diagram.md). If it returns errors, fix every one using its line, column, and hint and call again; nothing is drawn until the source is valid. Then use the pictures it returns. | | How do I put diagrams in a document? | Call [`sync_markdown`](https://docs.plotwork.dev/mcp/tools/sync-markdown.md) with the document and its path. It returns the rewritten document and the SVG files to write. Set `convertMermaid` to replace Mermaid fences first. | | When do I use a single tool? | For one fine-grained step: [`validate_diagram`](https://docs.plotwork.dev/mcp/tools/validate-diagram.md) to check, [`outline_diagram`](https://docs.plotwork.dev/mcp/tools/outline-diagram.md) to describe, [`render_diagram`](https://docs.plotwork.dev/mcp/tools/render-diagram.md) to draw one theme, [`search_icons`](https://docs.plotwork.dev/mcp/tools/search-icons.md) for one query. | | Do any tools change anything? | Not the tools on this page: each is read-only, idempotent, and closed-world, and none saves a file. Workflows return files as data for the caller to write. The hosted server adds three tools that work on your account: `publish_diagram` stores a diagram, `list_diagrams` reads your library, and `delete_diagram` removes a diagram you name. They are described in the MCP server guide. | | What happens on bad source? | A workflow returns the errors as data. `validate_diagram` returns them as data too. `render_diagram` and `outline_diagram` fail with the same diagnostics. | | Where do I get this as data? | An MCP client reads every name, description, and JSON Schema from `tools/list`. The language itself comes from `get_diagram_syntax`, or from `GET /v1/vocabulary` over HTTP. | ## Workflows A workflow performs several engine operations in one request. Each declares the operations it may call and the most it can perform, and a test runs every workflow with its widest input and fails if it calls an undeclared operation or passes its ceiling. An agent never has to guess: an ambiguous input comes back as candidates, and a mistake in the input comes back as data. | Workflow | Operations | Most operations per call | Replaces | |---|---|---|---| | [`draw_diagram`](https://docs.plotwork.dev/mcp/tools/draw-diagram.md) | `validate`, `outline`, `render` | 4 | validate_diagram, outline_diagram and render_diagram calls | | [`edit_diagram`](https://docs.plotwork.dev/mcp/tools/edit-diagram.md) | `parse`, `outline`, `validate`, `render` | 4 | rewriting the source by hand and the draw_diagram check | | [`convert_mermaid`](https://docs.plotwork.dev/mcp/tools/convert-mermaid.md) | `convertMermaid`, `validate`, `outline`, `render` | 5 | convert, validate_diagram, outline_diagram and render_diagram calls | | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | `importSource`, `validate`, `outline` | 26 | validate_diagram and outline_diagram | | [`map_icons`](https://docs.plotwork.dev/mcp/tools/map-icons.md) | `searchIcons` | 80 | up to 40 search_icons calls | | [`sync_markdown`](https://docs.plotwork.dev/mcp/tools/sync-markdown.md) | `convertMermaidFences`, `renderPair` | 31 | convert, validate, render and sync steps for every fence in a document | | [`suggest_diagram`](https://docs.plotwork.dev/mcp/tools/suggest-diagram.md) | `parse` | 3 | reading every kind in get_diagram_syntax to choose one | | [`plan_document_diagrams`](https://docs.plotwork.dev/mcp/tools/plan-document-diagrams.md) | `parse` | 12 | reading the document section by section and choosing a kind for each | | [`find_pattern`](https://docs.plotwork.dev/mcp/tools/find-pattern.md) | `findPatterns` | 1 | searching the documentation for an example to copy | | [`get_pattern`](https://docs.plotwork.dev/mcp/tools/get-pattern.md) | `fillPattern` | 1 | searching for an example and copying it by hand | | [`draw_from_pattern`](https://docs.plotwork.dev/mcp/tools/draw-from-pattern.md) | `fillPattern`, `validate`, `outline`, `render` | 5 | get_pattern, validate_diagram, outline_diagram and render_diagram calls | ## Single tools Each single tool does one thing. Use one when a workflow does more than you need. | Tool | Title | What it does | |---|---|---| | [`read_diagram`](https://docs.plotwork.dev/mcp/tools/read-diagram.md) | Read diagram | Read the source of an existing Plotwork SVG so you can edit the diagram | | [`compare_diagrams`](https://docs.plotwork.dev/mcp/tools/compare-diagrams.md) | Compare diagrams | Compare two versions of a diagram by meaning, for a pull request review | | [`get_diagram_syntax`](https://docs.plotwork.dev/mcp/tools/get-diagram-syntax.md) | Get diagram syntax | Read the diagram language as data | | [`validate_diagram`](https://docs.plotwork.dev/mcp/tools/validate-diagram.md) | Validate diagram | Check diagram source without drawing it: every error in one pass, each with line, column, and a fix hint, plus quality warnings | | [`render_diagram`](https://docs.plotwork.dev/mcp/tools/render-diagram.md) | Render diagram | Render valid source to a self-contained, accessible SVG in the light or dark theme, with its pixel size and quality warnings | | [`outline_diagram`](https://docs.plotwork.dev/mcp/tools/outline-diagram.md) | Outline diagram | Describe valid diagram source as plain text: participants, steps, tables, and connections in reading order, to check the diagram says what you meant or to use as alt text | | [`search_icons`](https://docs.plotwork.dev/mcp/tools/search-icons.md) | Search icons | Find bundled icon names by keyword, idea, product, or abbreviation ("postgres", "token exchange", "s3", "cosmos db") | ## Bounds Nothing a workflow does per item is unbounded. A bound that is reached is reported in the result; nothing is cut silently. Each tool page lists the bounds that apply to it. | Bound | Workflow | Value | What it limits | |---|---|---|---| | `maxIconNames` | [`map_icons`](https://docs.plotwork.dev/mcp/tools/map-icons.md) | 40 | Names in one call. | | `maxIconNameLength` | [`map_icons`](https://docs.plotwork.dev/mcp/tools/map-icons.md) | 80 | Characters in one name. | | `maxIconAlternatives` | [`map_icons`](https://docs.plotwork.dev/mcp/tools/map-icons.md) | 3 | Other icons listed beside a chosen icon. | | `maxIconCandidates` | [`map_icons`](https://docs.plotwork.dev/mcp/tools/map-icons.md) | 4 | Icons listed for a name that could mean several. | | `maxSummaryOutlineLines` | [`draw_diagram`](https://docs.plotwork.dev/mcp/tools/draw-diagram.md) | 40 | Outline lines in a summary; detail "full" returns all of them. | | `maxMarkdownLength` | [`sync_markdown`](https://docs.plotwork.dev/mcp/tools/sync-markdown.md) | 10000000 | Characters in the document. | | `maxDiagrams` | [`sync_markdown`](https://docs.plotwork.dev/mcp/tools/sync-markdown.md) | 30 | Diagram fences, plus mermaid fences when converting, handled in one call; the rest are left alone and reported. | | `maxOutputBytes` | [`sync_markdown`](https://docs.plotwork.dev/mcp/tools/sync-markdown.md) | 2000000 | Bytes in the returned document and files together; fences that would pass it are not drawn and are reported. | | `maxPatternQueryLength` | [`find_pattern`](https://docs.plotwork.dev/mcp/tools/find-pattern.md) | 200 | Characters in the query. | | `maxPatternTags` | [`find_pattern`](https://docs.plotwork.dev/mcp/tools/find-pattern.md) | 5 | Tags a search can require. | | `maxPatternResults` | [`find_pattern`](https://docs.plotwork.dev/mcp/tools/find-pattern.md) | 5 | Matches in one answer. | | `maxPatternSlots` | [`get_pattern`](https://docs.plotwork.dev/mcp/tools/get-pattern.md) | 40 | Slot values in one call; draw_from_pattern takes the same. | | `maxPathLength` | [`sync_markdown`](https://docs.plotwork.dev/mcp/tools/sync-markdown.md) | 300 | Characters in the document path or the assets directory. | | `maxPrefixLength` | [`sync_markdown`](https://docs.plotwork.dev/mcp/tools/sync-markdown.md) | 32 | Characters in the file name prefix. | | `maxRequestLength` | [`suggest_diagram`](https://docs.plotwork.dev/mcp/tools/suggest-diagram.md) | 1000 | Characters in the request. | | `maxSuggestDataLength` | [`suggest_diagram`](https://docs.plotwork.dev/mcp/tools/suggest-diagram.md) | 20000 | Characters in the pasted data. | | `maxSuggestions` | [`suggest_diagram`](https://docs.plotwork.dev/mcp/tools/suggest-diagram.md) | 3 | Kinds suggested in one answer. | | `maxPlanDocumentLength` | [`plan_document_diagrams`](https://docs.plotwork.dev/mcp/tools/plan-document-diagrams.md) | 10000000 | Characters in the document. | | `maxPlanSections` | [`plan_document_diagrams`](https://docs.plotwork.dev/mcp/tools/plan-document-diagrams.md) | 200 | Sections read; a document with more says so in caps. | | `maxPlanSuggestions` | [`plan_document_diagrams`](https://docs.plotwork.dev/mcp/tools/plan-document-diagrams.md) | 12 | Sections suggested in one answer, the strongest first; the rest are counted in caps. | | `maxStarterItems` | [`suggest_diagram`](https://docs.plotwork.dev/mcp/tools/suggest-diagram.md) | 8 | Items, rows, or columns a starter takes from the text; plan_document_diagrams takes the same. | | `maxEditOperations` | [`edit_diagram`](https://docs.plotwork.dev/mcp/tools/edit-diagram.md) | 50 | Operations in one call. | | `maxImportLength` | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | 10000000 | Characters an import reads, not counting white space or lines that are only a comment. A longer text is answered with I102. | | `maxImportLocalLength` | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | 100000000 | Characters of text in one call on the local server, which runs on your own machine. A guard on memory, not a cap a real project meets. | | `maxImportTextLength` | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | 10485760 | Characters of text in one hosted call, comments and white space included, all files together. | | `maxImportFiles` | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | 200 | Files in one hosted call. | | `maxImportLocalFiles` | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | 20000 | Files in one call on the local server. | | `maxImportParts` | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | 24 | Diagrams one import is split into when the file is too big for one picture; each is validated. | | `maxImportNodes` | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | 40 | Boxes in an imported flow; the rest are left out with an I200 diagnostic. | | `maxImportTables` | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | 20 | Tables in an imported schema. | | `maxImportRows` | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | 20 | Rows of an imported matrix: endpoints, or rows of a table. | | `maxImportColumns` | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | 8 | Columns of an imported matrix: roles, scopes, or schemes. | | `maxImportPoints` | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | 60 | Places, slices, flows, or tiles in an imported chart. | | `maxImportSeries` | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | 6 | Series in an imported XY chart. | | `maxImportEntries` | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | 60 | Rows of an imported tree; a folder past it ends with an omitted line. | | `maxImportCommits` | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | 40 | Commits of an imported git graph, the newest kept, and events of an imported timeline. | | `maxImportMessages` | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | 30 | Messages of an imported sequence. | | `maxEditTextLength` | [`edit_diagram`](https://docs.plotwork.dev/mcp/tools/edit-diagram.md) | 2000 | Characters in one field of an operation, such as a statement, a find, or a replacement. | | `maxCompareChanges` | [`compare_diagrams`](https://docs.plotwork.dev/mcp/tools/compare-diagrams.md) | 200 | Changes listed in one answer; counts and the summary cover every change. | | `maxSyncPageSize` | [`sync_markdown`](https://docs.plotwork.dev/mcp/tools/sync-markdown.md) | 30 | Pictures in one page of files, with page and pageSize. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | The same operations over HTTP, one page for each endpoint | | [Language reference](https://docs.plotwork.dev/reference/language.md) | What the diagram source may contain | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop in practice | --- # draw_diagram > Page: https://docs.plotwork.dev/mcp/tools/draw-diagram. Index of every page: https://docs.plotwork.dev/llms.txt Checks, describes, and draws a diagram. It is a workflow of the MCP server and the HTTP API, with the title "Draw diagram". ## TL;DR | Question | Answer | |---|---| | What is it? | A workflow: one call does the work of several tools and returns one combined answer. | | What does it do? | Checks, describes, and draws a diagram. | | What does it replace? | validate_diagram, outline_diagram and render_diagram calls. | | How do I call it over HTTP? | [`POST /v1/diagrams/draw`](https://docs.plotwork.dev/api/post-v1-diagrams-draw.md) | | Does it change anything? | No. It is read-only and idempotent, and it talks to nothing outside itself. It saves no file and returns its answer as data. | | Which bounds apply? | `maxSummaryOutlineLines`. See the bounds below. | | Where do I read its schema as data? | An MCP client gets the name, description, and JSON Schema of every tool from `tools/list`. | ## What it does Workflow. Checks, describes, and draws a diagram. With errors it returns each one with line, column, and a fix hint and draws nothing: fix the source and call again. When valid it returns the quality warnings (take the icon and group-type suggestions), an outline to check the meaning, the size, and the SVG for light, dark, or both. The pictures are most of an answer and you cannot see them: pass svg "never" to check, and draw only to save, show, or publish. Give viewUrl to the person, even after svg "never"; pngUrl and svgUrl download it; saveCommand, when given, saves it in a repository. A big drawing (about 30 nodes) loses its SVGs, dark first, and picturesOmitted says so; svg "always" forces them. Set embedSource true to save an SVG in a repository or publish one to edit later, so read_diagram gives the source back (anyone with the file can read it). detail "full" adds the whole outline. To change an existing diagram use edit_diagram, or read_diagram for a picture. Saves no file; never throws on bad source. Replaces the validate_diagram, outline_diagram and render_diagram calls. ## Steps | Operation | Runs | What it does | |---|---|---| | `validate` | always | Parse the source and lint it: every error, and the quality, icon, and group-type warnings. | | `outline` | only when needed | Describe a valid diagram as plain text. | | `render` | only when needed | Lay out and paint a valid diagram in one color scheme. | One call performs at most 4 operations. ## Example call The arguments of the call, as JSON: ```json { "source": "flow \"Login\" direction=right\nnode user person \"User\"\nnode api service \"Auth API\"\nnode db database \"Users DB\"\nuser -> api : \"POST /login\"\napi -> db : \"look up user\"", "themes": "both" } ``` The `source` argument, written out one line at a time: ```plotwork flow "Login" direction=right node user person "User" node api service "Auth API" node db database "Users DB" user -> api : "POST /login" api -> db : "look up user" ``` ## Prompts that lead to it - Draw a diagram of how a user signs in through an API that checks a database. - Check this diagram and draw it in light and dark. ## Arguments | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | Diagram source. The first line is the header: flow "Title", sequence "Title", schema "Title", git "Title", gantt "Title", class "Title", state "Title", timeline "Title", journey "Title", mindmap "Title", treemap "Title", pie "Title", quadrant "Title", layers "Title", c4 "Title", matrix "Title", sankey "Title", xychart "Title", tree "Title", or swimlane "Title". at least 1 character; at most 50000 characters. | | `themes` | `"light" \| "dark" \| "both"` | no | light, dark, or both (the default): the light picture and the dark one a dark page needs. default "both". | | `detail` | `"summary" \| "full"` | no | summary (the default), or full: the whole outline, and the source embedded in each SVG. default "summary". | | `svg` | `"auto" \| "always" \| "never"` | no | auto (the default) returns the SVGs, but over MCP leaves them out when the answer would be too big for a client (picturesOmitted). always returns them at any size. never draws nothing, which is cheaper. The HTTP API treats auto as always. default "auto". | | `embedSource` | `boolean` | no | Write the source into each SVG so read_diagram can give it back. Set it when you save the SVG in a repository or publish it to edit later; anyone with the file or link can then read the source. default false. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## Result | Field | Type | Required | Description | |---|---|---|---| | `valid` | `boolean` | yes | True when the source has no errors. When false, nothing was drawn: fix the errors and call again. | | `kind` | `string \| null` | yes | The diagram kind, or null when the header could not be read. | | `errors` | `Diagnostic[]` | yes | Every error found in one pass, each with line, column, and a hint. Empty when valid. | | `errors[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `errors[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `errors[].message` | `string` | yes | What is wrong, in one sentence. | | `errors[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `errors[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `errors[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `errors[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `warnings` | `Diagnostic[]` | yes | Quality warnings for a valid diagram: too dense, too wide, text cut, unconnected nodes, and suggestions for an icon or a group type. Empty when invalid. | | `warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `summary` | `string \| null` | yes | One-line description of the diagram, usable as alt text; null when invalid. | | `outline` | `string[]` | yes | The diagram as plain text in reading order, to check it says what you meant; empty when invalid. | | `outlineTruncated` | `boolean` | yes | True when the summary level cut the outline short. Call again with detail "full" to read all of it. | | `size` | `Size \| null` | yes | Natural size of the laid-out diagram; null when invalid. | | `quality` | `Quality` | no | How clean the picture looks (score, grade, measures) and up to three changes that would help; read it instead of looking. Absent when invalid. | | `quality.score` | `integer` | yes | 0 to 100; 100 is a picture with nothing to fix. The same diagram always gets the same score. minimum 0; maximum 100. | | `quality.grade` | `"clean" \| "fair" \| "cluttered"` | yes | clean is 85 or more, fair 60 or more, cluttered below. | | `quality.measures` | `object` | yes | What the score is made from: crossings, lineOverBox, labelOverlaps, smallestTextPx (in an 880px column), aspectRatio, emptySpace (0 to 1), longestLine (px), detour (1 is straight), the counts of boxes, lines, labels, series, and slices, and any measure the kind adds. | | `quality.hints` | `string[]` | yes | Up to three changes, the one worth most first, each naming the statement or option to edit. | | `quality.line` | `string` | yes | The grade, the score, and the first hint in one line. | | `notices` | `Note[]` | no | Information notes, such as an icon alias drawn as the real icon; absent when there is none. | | `notices[].code` | `string` | yes | A stable code, such as I122 for an icon alias. | | `notices[].severity` | `"info"` | yes | A note never stops a drawing and is not a warning. | | `notices[].message` | `string` | yes | | | `notices[].line` | `integer` | yes | minimum 1. | | `notices[].column` | `integer` | yes | minimum 1. | | `notices[].length` | `integer` | yes | minimum 1. | | `notices[].hint` | `string \| null` | yes | The text to write instead. | | `renders` | `object[]` | yes | One picture per requested color scheme, light first; empty when invalid, when svg is never, and when every picture was left out (see picturesOmitted). | | `renders[].theme` | `"light" \| "dark"` | yes | The color scheme this picture is drawn in. | | `renders[].svg` | `string` | yes | Self-contained SVG markup with an accessible title and description. | | `renders[].size` | `Size` | yes | Natural size of a rendered diagram in px. | | `renders[].size.width` | `integer` | yes | Width in px. minimum 1. | | `renders[].size.height` | `integer` | yes | Height in px. minimum 1. | | `picturesOmitted` | `object` | no | Present only when pictures were left out because the answer would have been too big for an AI client to read in one piece. | | `picturesOmitted.themes` | `("light" \| "dark")[]` | yes | The color schemes whose pictures were left out. | | `picturesOmitted.characters` | `integer` | yes | How many characters the pictures left out came to. | | `picturesOmitted.limit` | `integer` | yes | The most characters one answer may hold before the pictures are left out. | | `picturesOmitted.why` | `string` | yes | Why they were left out, and what to do. | | `viewUrl` | `string` | no | A link that shows a valid diagram in a browser; give it to the person who wants to look. The source is in the part after #, never sent to a server, but anyone with the link can read it. Absent when too long; next says so. | | `pngUrl` | `string` | no | The viewUrl with &download=png: opening it in a browser saves the diagram as a PNG for Slack, email, or a wiki. The PNG is drawn in the person's browser, not on a server, so it is a link to open, not an image address to embed. Absent when too long. | | `svgUrl` | `string` | no | The viewUrl with &download=svg: a person opens it to download the SVG. Not a file address for curl; use saveCommand. Absent when too long. | | `saveCommand` | `string` | no | Run in a shell to save the SVG into the repository: the CLI decodes the link locally, fetches nothing, and embeds the source. Set --out to the path; add --theme dark for dark. Absent when too long. | | `next` | `string` | yes | The next step in one sentence. | ## Behavior hints | Hint | Value | |---|---| | `readOnlyHint` | true | | `destructiveHint` | false | | `idempotentHint` | true | | `openWorldHint` | false | ## Bounds A bound that is reached is reported in the result; nothing is cut silently. | Bound | Value | What it limits | |---|---|---| | `maxSummaryOutlineLines` | 40 | Outline lines in a summary; detail "full" returns all of them. | ## See also | Doc | Why | |---|---| | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Every tool, with the workflows first | | [`POST /v1/diagrams/draw`](https://docs.plotwork.dev/api/post-v1-diagrams-draw.md) | The same operation over HTTP | | [Language reference](https://docs.plotwork.dev/reference/language.md) | What the diagram source may contain | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop in practice | --- # edit_diagram > Page: https://docs.plotwork.dev/mcp/tools/edit-diagram. Index of every page: https://docs.plotwork.dev/llms.txt Changes diagram source with typed operations, on every kind, and checks the result. It is a workflow of the MCP server and the HTTP API, with the title "Edit diagram". ## TL;DR | Question | Answer | |---|---| | What is it? | A workflow: one call does the work of several tools and returns one combined answer. | | What does it do? | Changes diagram source with typed operations, on every kind, and checks the result. | | What does it replace? | rewriting the source by hand and the draw_diagram check. | | How do I call it over HTTP? | [`POST /v1/diagrams/edit`](https://docs.plotwork.dev/api/post-v1-diagrams-edit.md) | | Does it change anything? | No. It is read-only and idempotent, and it talks to nothing outside itself. It saves no file and returns its answer as data. | | Which bounds apply? | `maxEditOperations`, `maxEditTextLength`. See the bounds below. | | Where do I read its schema as data? | An MCP client gets the name, description, and JSON Schema of every tool from `tools/list`. | ## What it does Workflow. Changes diagram source with typed operations, on every kind, and checks the result. Each op and its fields: rename (target, to: an id everywhere it is used, or a label), set (target, or from and to for an edge, or target "header"; option, value, null removes it), add_node (id, label, type, options; into a block or after a statement; statement for a whole line), remove (target, with its block and edges), add_edge (from, to, label, arrow, options), remove_edge (from, to, label), move (target, into: a block, or null for the top level), replace_text (find, replace, all). Untouched lines keep their comments and layout. Each op reports applied, or why not with a hint, and a failed one is skipped. svg "never" (the default) lays nothing out; auto or always draws as draw_diagram does, with viewUrl and saveCommand. Replaces rewriting the source by hand and the draw_diagram check. ## Steps | Operation | Runs | What it does | |---|---|---| | `parse` | only when needed | Parse the source and return its errors, without laying it out or linting it. | | `outline` | only when needed | Describe a valid diagram as plain text. | | `validate` | only when needed | Parse the source and lint it: every error, and the quality, icon, and group-type warnings. | | `render` | only when needed | Lay out and paint a valid diagram in one color scheme. | One call performs at most 4 operations. ## Example call The arguments of the call, as JSON: ```json { "source": "flow \"Login\" direction=right\n# the sign-in path\nnode user person \"User\"\nnode api service \"Auth API\"\nnode db database \"Users DB\"\nuser -> api : \"POST /login\"\napi -> db : \"look up user\"", "operations": [ { "op": "rename", "target": "api", "to": "gateway" }, { "op": "set", "target": "db", "option": "icon", "value": "simple:postgresql" }, { "op": "add_node", "id": "cache", "type": "database", "label": "Session cache" }, { "op": "add_edge", "from": "gateway", "to": "cache", "label": "store session" } ] } ``` The `source` argument, written out one line at a time: ```plotwork flow "Login" direction=right # the sign-in path node user person "User" node api service "Auth API" node db database "Users DB" user -> api : "POST /login" api -> db : "look up user" ``` ## Prompts that lead to it - Rename the Auth API node to gateway and add a session cache it writes to. - Move the database into the data group and remove the old queue. ## Arguments | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | Diagram source; the first line is the header, such as flow "Title" (draw_diagram lists every kind). at least 1 character; at most 50000 characters. | | `operations` | `object[]` | yes | Applied in order. | | `operations[].op` | `"rename" \| "set" \| "add_node" \| "remove" \| "add_edge" \| "remove_edge" \| "move" \| "replace_text"` | yes | | | `operations[].target` | `string` | no | Id, label, table.column, or "header". at most 2000 characters. | | `operations[].to` | `string` | no | rename: the new name. Edges: the end. at most 2000 characters. | | `operations[].from` | `string` | no | Edges: the start. at most 2000 characters. | | `operations[].option` | `string` | no | at most 40 characters; pattern `^[a-z][A-Za-z0-9_-]*$`. | | `operations[].value` | `string \| null` | no | set: null removes the option. | | `operations[].id` | `string` | no | at most 2000 characters. | | `operations[].label` | `string` | no | add_edge on sankey: the amount. at most 2000 characters. | | `operations[].type` | `string` | no | add_node: node type, or statement word (c4 person, tree dir). at most 2000 characters. | | `operations[].options` | `object` | no | | | `operations[].statement` | `string` | no | add_node: the whole line, for a kind with no plain node statement. at most 2000 characters. | | `operations[].into` | `string \| null` | no | A block; null is the top level. | | `operations[].after` | `string` | no | at most 2000 characters. | | `operations[].arrow` | `"<-->" \| "-->" \| "<->" \| "..>" \| "->" \| "--"` | no | | | `operations[].find` | `string` | no | at most 2000 characters. | | `operations[].replace` | `string` | no | at most 2000 characters. | | `operations[].all` | `boolean` | no | | | `svg` | `"auto" \| "always" \| "never"` | no | never (the default) draws nothing; auto or always draws like draw_diagram. default "never". | | `themes` | `"light" \| "dark" \| "both"` | no | light, dark, or both (the default): the light picture and the dark one a dark page needs. default "both". | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## Result | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | The edited source. Lines no operation touched are byte for byte as they were, comments included. | | `changed` | `boolean` | yes | | | `results` | `object[]` | yes | One entry for each operation, in order. | | `results[].op` | `"rename" \| "set" \| "add_node" \| "remove" \| "add_edge" \| "remove_edge" \| "move" \| "replace_text"` | yes | | | `results[].applied` | `boolean` | yes | | | `results[].lines` | `integer[]` | yes | Lines of the new source this operation wrote, or the lines it removed from. | | `results[].message` | `string` | yes | | | `results[].hint` | `string \| null` | yes | | | `valid` | `boolean` | yes | | | `kind` | `string \| null` | yes | | | `errors` | `Diagnostic[]` | yes | | | `errors[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `errors[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `errors[].message` | `string` | yes | What is wrong, in one sentence. | | `errors[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `errors[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `errors[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `errors[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `warnings` | `Diagnostic[]` | yes | Quality warnings; only when the result is drawn. | | `warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `summary` | `string \| null` | yes | | | `outline` | `string[]` | yes | | | `outlineTruncated` | `boolean` | yes | | | `size` | `Size \| null` | yes | | | `renders` | `object[]` | yes | | | `renders[].theme` | `"light" \| "dark"` | yes | | | `renders[].svg` | `string` | yes | | | `renders[].size` | `Size` | yes | Natural size of a rendered diagram in px. | | `renders[].size.width` | `integer` | yes | Width in px. minimum 1. | | `renders[].size.height` | `integer` | yes | Height in px. minimum 1. | | `picturesOmitted` | `object` | no | Present only when pictures were left out because the answer would have been too big for an AI client to read in one piece. | | `picturesOmitted.themes` | `("light" \| "dark")[]` | yes | The color schemes whose pictures were left out. | | `picturesOmitted.characters` | `integer` | yes | How many characters the pictures left out came to. | | `picturesOmitted.limit` | `integer` | yes | The most characters one answer may hold before the pictures are left out. | | `picturesOmitted.why` | `string` | yes | Why they were left out, and what to do. | | `viewUrl` | `string` | no | A link that shows a valid diagram in a browser; give it to the person who wants to look. The source is in the part after #, never sent to a server, but anyone with the link can read it. Absent when too long; next says so. | | `svgUrl` | `string` | no | The viewUrl with &download=svg: a person opens it to download the SVG. Not a file address for curl; use saveCommand. Absent when too long. | | `saveCommand` | `string` | no | Run in a shell to save the SVG into the repository: the CLI decodes the link locally, fetches nothing, and embeds the source. Set --out to the path; add --theme dark for dark. Absent when too long. | | `next` | `string` | yes | | ## Behavior hints | Hint | Value | |---|---| | `readOnlyHint` | true | | `destructiveHint` | false | | `idempotentHint` | true | | `openWorldHint` | false | ## Bounds A bound that is reached is reported in the result; nothing is cut silently. | Bound | Value | What it limits | |---|---|---| | `maxEditOperations` | 50 | Operations in one call. | | `maxEditTextLength` | 2000 | Characters in one field of an operation, such as a statement, a find, or a replacement. | ## See also | Doc | Why | |---|---| | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Every tool, with the workflows first | | [`POST /v1/diagrams/edit`](https://docs.plotwork.dev/api/post-v1-diagrams-edit.md) | The same operation over HTTP | | [Language reference](https://docs.plotwork.dev/reference/language.md) | What the diagram source may contain | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop in practice | --- # convert_mermaid > Page: https://docs.plotwork.dev/mcp/tools/convert-mermaid. Index of every page: https://docs.plotwork.dev/llms.txt Converts one Mermaid diagram (flowchart or graph, sequenceDiagram, erDiagram, gitGraph, gantt, classDiagram, stateDiagram, timeline, journey, mindmap, treemap-beta or treemap, pie, quadrantChart, C4Context or C4Container or C4Component or C4Dynamic or C4Deployment, sankey-beta or sankey, xychart-beta or xychart, treeView-beta or treeView) to Plotwork source. It is a workflow of the MCP server and the HTTP API, with the title "Convert Mermaid". ## TL;DR | Question | Answer | |---|---| | What is it? | A workflow: one call does the work of several tools and returns one combined answer. | | What does it do? | Converts one Mermaid diagram (flowchart or graph, sequenceDiagram, erDiagram, gitGraph, gantt, classDiagram, stateDiagram, timeline, journey, mindmap, treemap-beta or treemap, pie, quadrantChart, C4Context or C4Container or C4Component or C4Dynamic or C4Deployment, sankey-beta or sankey, xychart-beta or xychart, treeView-beta or treeView) to Plotwork source. | | What does it replace? | convert, validate_diagram, outline_diagram and render_diagram calls. | | How do I call it over HTTP? | [`POST /v1/diagrams/convert-mermaid`](https://docs.plotwork.dev/api/post-v1-diagrams-convert-mermaid.md) | | Does it change anything? | No. It is read-only and idempotent, and it talks to nothing outside itself. It saves no file and returns its answer as data. | | Which bounds apply? | None beyond the limits of each argument, which are in the schema below. | | Where do I read its schema as data? | An MCP client gets the name, description, and JSON Schema of every tool from `tools/list`. | ## What it does Workflow. Converts one Mermaid diagram (flowchart or graph, sequenceDiagram, erDiagram, gitGraph, gantt, classDiagram, stateDiagram, timeline, journey, mindmap, treemap-beta or treemap, pie, quadrantChart, C4Context or C4Container or C4Component or C4Dynamic or C4Deployment, sankey-beta or sankey, xychart-beta or xychart, treeView-beta or treeView) to Plotwork source. Returns the source, a diagnostic with a hint for everything dropped or changed (styling, links, activations), the check of the result with its icon and group-type suggestions, and an outline; render true also draws it, with picturesOmitted, viewUrl, pngUrl, and saveCommand as in draw_diagram. A Mermaid type Plotwork does not draw comes back as one diagnostic saying what to do instead. Deterministic: never throws on bad input, does not fetch URLs or read files. For a whole Markdown document use sync_markdown with convertMermaid true. Replaces the convert, validate_diagram, outline_diagram and render_diagram calls. ## Steps | Operation | Runs | What it does | |---|---|---| | `convertMermaid` | always | Convert one Mermaid diagram to Plotwork source. | | `validate` | only when needed | Parse the source and lint it: every error, and the quality, icon, and group-type warnings. | | `outline` | only when needed | Describe a valid diagram as plain text. | | `render` | only when needed | Lay out and paint a valid diagram in one color scheme. | One call performs at most 5 operations. ## Example call The arguments of the call, as JSON: ```json { "source": "flowchart LR\n user([User]) --> api[Auth API]\n subgraph data [Data]\n db[(Users DB)]\n end\n api -->|look up| db\n api --> check{Valid?}\n check -->|yes| ok([Signed in])", "render": false } ``` The `source` argument, written out one line at a time: ```mermaid-source flowchart LR user([User]) --> api[Auth API] subgraph data [Data] db[(Users DB)] end api -->|look up| db api --> check{Valid?} check -->|yes| ok([Signed in]) ``` ## Prompts that lead to it - Convert this Mermaid flowchart to Plotwork. - Migrate this Mermaid diagram to Plotwork and tell me what was lost. ## Arguments | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | One Mermaid diagram: a flowchart or graph, sequenceDiagram, erDiagram, gitGraph, gantt, classDiagram, stateDiagram, timeline, journey, mindmap, treemap-beta or treemap, pie, quadrantChart, C4Context or C4Container or C4Component or C4Dynamic or C4Deployment, sankey-beta or sankey, xychart-beta or xychart, or treeView-beta or treeView. Front matter and %%{init}%% directives are allowed and are dropped. Send the text inside a mermaid fence, without the fence. at least 1 character; at most 50000 characters. | | `lenient` | `boolean` | no | false (the default): a line that cannot be read stops the conversion, so nothing is converted with a line missing. true: such a line is skipped with a warning and the rest is converted. default false. | | `render` | `boolean` | no | Also draw the converted diagram, in the color schemes themes names. default false. | | `themes` | `"light" \| "dark" \| "both"` | no | light, dark, or both (the default): the light picture and the dark one a dark page needs. default "both". | | `detail` | `"summary" \| "full"` | no | summary (the default), or full: the whole outline, and the source embedded in each SVG. default "summary". | | `svg` | `"auto" \| "always" \| "never"` | no | auto (the default) returns the SVGs, but over MCP leaves them out when the answer would be too big for a client (picturesOmitted). always returns them at any size. never draws nothing, which is cheaper. The HTTP API treats auto as always. default "auto". | | `embedSource` | `boolean` | no | Write the source into each SVG so read_diagram can give it back. Set it when you save the SVG in a repository or publish it to edit later; anyone with the file or link can then read the source. default false. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## Result | Field | Type | Required | Description | |---|---|---|---| | `ok` | `boolean` | yes | True when `source` is valid Plotwork source. False means nothing was converted and `source` is empty: read `diagnostics`. | | `kind` | `string \| null` | yes | The Plotwork kind the diagram became, or null when it could not be converted. | | `mermaidType` | `string \| null` | yes | The Mermaid diagram type read from the first line, such as flowchart or sequenceDiagram; null when none was found. | | `source` | `string` | yes | The Plotwork source, or an empty string when nothing could be converted. | | `diagnostics` | `Diagnostic[]` | yes | Everything dropped, changed, or wrong, with a hint that says what to do. Codes start with M. Lines and columns point into the Mermaid you sent. M1xx codes are errors that stop the conversion; M2xx codes are warnings and the conversion went on. | | `diagnostics[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `diagnostics[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `diagnostics[].message` | `string` | yes | What is wrong, in one sentence. | | `diagnostics[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `diagnostics[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `diagnostics[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `diagnostics[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `stats` | `object` | yes | | | `stats.nodes` | `integer` | yes | Flow nodes written. | | `stats.edges` | `integer` | yes | Flow edges written. | | `stats.groups` | `integer` | yes | Flow groups written. | | `stats.actors` | `integer` | yes | Sequence participants written. | | `stats.messages` | `integer` | yes | Sequence messages written. | | `stats.tables` | `integer` | yes | Schema tables written. | | `stats.columns` | `integer` | yes | Schema columns written. | | `stats.relations` | `integer` | yes | Schema relations written. | | `stats.dropped` | `integer` | yes | Warnings about constructs that were dropped: styling, links, activations, and so on. | | `stats.renamed` | `integer` | yes | Ids that were not valid Plotwork ids and were renamed. | | `validation` | `object \| null` | yes | The Plotwork validator run on `source`; null when nothing was converted. | | `quality` | `Quality` | no | How clean the converted diagram looks and up to three changes that would help (see draw_diagram); absent when it is not valid. | | `quality.score` | `integer` | yes | 0 to 100; 100 is a picture with nothing to fix. The same diagram always gets the same score. minimum 0; maximum 100. | | `quality.grade` | `"clean" \| "fair" \| "cluttered"` | yes | clean is 85 or more, fair 60 or more, cluttered below. | | `quality.measures` | `object` | yes | What the score is made from: crossings, lineOverBox, labelOverlaps, smallestTextPx (in an 880px column), aspectRatio, emptySpace (0 to 1), longestLine (px), detour (1 is straight), the counts of boxes, lines, labels, series, and slices, and any measure the kind adds. | | `quality.hints` | `string[]` | yes | Up to three changes, the one worth most first, each naming the statement or option to edit. | | `quality.line` | `string` | yes | The grade, the score, and the first hint in one line. | | `outline` | `string[]` | yes | The converted diagram as plain text, to check it says what the Mermaid said; empty when nothing was converted. | | `outlineTruncated` | `boolean` | yes | True when the summary level cut the outline short. Call again with detail "full" to read all of it. | | `renders` | `object[]` | yes | One picture per requested color scheme, light first; empty unless `render` was true and the conversion is valid, when svg is never, and when every picture was left out (see picturesOmitted). | | `renders[].theme` | `"light" \| "dark"` | yes | The color scheme this picture is drawn in. | | `renders[].svg` | `string` | yes | Self-contained SVG markup with an accessible title and description. | | `renders[].size` | `Size` | yes | Natural size of a rendered diagram in px. | | `renders[].size.width` | `integer` | yes | Width in px. minimum 1. | | `renders[].size.height` | `integer` | yes | Height in px. minimum 1. | | `picturesOmitted` | `object` | no | Present only when pictures were left out because the answer would have been too big for an AI client to read in one piece. | | `picturesOmitted.themes` | `("light" \| "dark")[]` | yes | The color schemes whose pictures were left out. | | `picturesOmitted.characters` | `integer` | yes | How many characters the pictures left out came to. | | `picturesOmitted.limit` | `integer` | yes | The most characters one answer may hold before the pictures are left out. | | `picturesOmitted.why` | `string` | yes | Why they were left out, and what to do. | | `viewUrl` | `string` | no | A link that shows a valid diagram in a browser; give it to the person who wants to look. The source is in the part after #, never sent to a server, but anyone with the link can read it. Absent when too long; next says so. | | `pngUrl` | `string` | no | The viewUrl with &download=png: opening it in a browser saves the diagram as a PNG for Slack, email, or a wiki. The PNG is drawn in the person's browser, not on a server, so it is a link to open, not an image address to embed. Absent when too long. | | `svgUrl` | `string` | no | The viewUrl with &download=svg: a person opens it to download the SVG. Not a file address for curl; use saveCommand. Absent when too long. | | `saveCommand` | `string` | no | Run in a shell to save the SVG into the repository: the CLI decodes the link locally, fetches nothing, and embeds the source. Set --out to the path; add --theme dark for dark. Absent when too long. | | `next` | `string` | yes | The next step in one sentence. | ## Behavior hints | Hint | Value | |---|---| | `readOnlyHint` | true | | `destructiveHint` | false | | `idempotentHint` | true | | `openWorldHint` | false | ## See also | Doc | Why | |---|---| | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Every tool, with the workflows first | | [`POST /v1/diagrams/convert-mermaid`](https://docs.plotwork.dev/api/post-v1-diagrams-convert-mermaid.md) | The same operation over HTTP | | [Language reference](https://docs.plotwork.dev/reference/language.md) | What the diagram source may contain | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop in practice | --- # import_source > Page: https://docs.plotwork.dev/mcp/tools/import-source. Index of every page: https://docs.plotwork.dev/llms.txt Turns a file into a finished diagram in one call: a database schema, an API spec, infrastructure as code, manifests, a software catalog, a CI workflow, CSV or JSON data, a folder listing, or a git log. It is a workflow of the MCP server and the HTTP API, with the title "Import source". ## TL;DR | Question | Answer | |---|---| | What is it? | A workflow: one call does the work of several tools and returns one combined answer. | | What does it do? | Turns a file into a finished diagram in one call: a database schema, an API spec, infrastructure as code, manifests, a software catalog, a CI workflow, CSV or JSON data, a folder listing, or a git log. | | What does it replace? | validate_diagram and outline_diagram. | | How do I call it over HTTP? | [`POST /v1/import`](https://docs.plotwork.dev/api/post-v1-import.md) | | Does it change anything? | No. It is read-only and idempotent, and it talks to nothing outside itself. It saves no file and returns its answer as data. | | Which bounds apply? | `maxImportLength`, `maxImportLocalLength`, `maxImportTextLength`, `maxImportFiles`, `maxImportLocalFiles`, `maxImportParts`, `maxImportNodes`, `maxImportTables`, `maxImportRows`, `maxImportColumns`, `maxImportPoints`, `maxImportSeries`, `maxImportEntries`, `maxImportCommits`, `maxImportMessages`. See the bounds below. | | Where do I read its schema as data? | An MCP client gets the name, description, and JSON Schema of every tool from `tools/list`. | ## What it does Workflow. Turns a file into a finished diagram in one call: a database schema, an API spec, infrastructure as code, manifests, a software catalog, a CI workflow, CSV or JSON data, a folder listing, or a git log. get_diagram_syntax with topic import-formats says what each format reads, draws, and takes. Prefer it to drawing by hand whenever the person has such a file: send its text, never a path, or every file of a folder in files. Returns the source, diagnostics pointing into the file for anything left out, coverage, parts (every diagram of a split file), the validation, an outline, viewUrl, and saveCommand. It runs no code and reads no file or URL. Replaces validate_diagram and outline_diagram. ## Steps | Operation | Runs | What it does | |---|---|---| | `importSource` | always | Read a file of another format (SQL, Prisma, Drizzle, OpenAPI, Terraform, a Terraform plan or state, Bicep, ARM, Pulumi, CloudFormation, CDK, Compose, Kubernetes, Helm, Kustomize, Backstage, Structurizr, AsyncAPI, GitHub Actions, GitLab CI/CD, Azure Pipelines, CSV, JSON, a tree, or a git log) into Plotwork source. | | `validate` | only when needed | Parse the source and lint it: every error, and the quality, icon, and group-type warnings. | | `outline` | only when needed | Describe a valid diagram as plain text. | One call performs at most 26 operations. ## Example call The arguments of the call, as JSON: ```json { "format": "compose", "text": "services:\n api:\n build: ./api\n ports: [\"8080:8080\"]\n depends_on: [db, cache]\n db:\n image: postgres:16\n cache:\n image: redis:7\n" } ``` The `text` argument, written out one line at a time: ```text services: api: build: ./api ports: ["8080:8080"] depends_on: [db, cache] db: image: postgres:16 cache: image: redis:7 ``` ## Prompts that lead to it - Draw our database schema from this SQL. - Make a diagram of this docker-compose file. - Which roles can call which endpoints in this OpenAPI spec? - Chart this CSV. ## Arguments | Field | Type | Required | Description | |---|---|---|---| | `format` | `"sql" \| "prisma" \| "drizzle" \| "openapi" \| "terraform" \| "terraform-plan" \| "bicep" \| "arm" \| "pulumi" \| "cloudformation" \| "cdk" \| "compose" \| "kubernetes" \| "helm" \| "kustomize" \| "backstage" \| "structurizr" \| "asyncapi" \| "github-actions" \| "gitlab-ci" \| "azure-pipelines" \| "csv" \| "json" \| "tree" \| "git-log"` | yes | What the text is; get_diagram_syntax with topic import-formats describes each. | | `text` | `string` | no | The file content, as text. Send text or files, not both. | | `files` | `object[]` | no | The files of a folder, for a format that spans files; the others read the first. | | `files[].path` | `string` | yes | The file path, such as infra/network.tf; diagnostics name it. at least 1 character; at most 300 characters. | | `files[].text` | `string` | yes | The file content, as text. | | `title` | `string` | no | The diagram title; taken from the file when left out. at most 200 characters. | | `partsPage` | `integer` | no | Which page of parts to return when a split import has more than 24 diagrams: 1 (default) is the first 24. minimum 1; maximum 100000. | | `options` | `object` | no | default {}. | | `options.view` | `"endpoints" \| "map" \| "sequence" \| "git" \| "timeline" \| "context" \| "container" \| "component"` | no | The picture of a format drawn several ways. | | `options.operationId` | `string` | no | openapi sequence: the operation. at most 200 characters. | | `options.chart` | `"auto" \| "xychart" \| "pie" \| "sankey" \| "treemap" \| "matrix"` | no | csv, json: the chart; auto picks. | | `options.tables` | `string[]` | no | Tables to draw, by name or prefix*. | | `options.paths` | `string[]` | no | openapi: path prefixes to read. | | `options.tags` | `string[]` | no | openapi: tags to read. | | `options.varFiles` | `string[]` | no | Variables files to apply. | | `options.groupBy` | `"module" \| "network" \| "file" \| "owner"` | no | How things are framed; module is the default. | | `options.root` | `string` | no | The root, stack, or template of several to draw. at most 300 characters. | ## Result | Field | Type | Required | Description | |---|---|---|---| | `ok` | `boolean` | yes | True when `source` is valid Plotwork source. False means nothing was imported and `source` is empty: read `diagnostics`. | | `kind` | `string \| null` | yes | The kind of diagram the file became, or null when nothing was imported. | | `format` | `string` | yes | The format the text was read as. | | `source` | `string` | yes | The Plotwork source, or an empty string when nothing could be imported. | | `diagnostics` | `Diagnostic[]` | yes | Everything left out, changed, or wrong, with a hint. Codes start with I and point into the text you sent: I1xx errors stopped the import, I2xx warnings did not. | | `diagnostics[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `diagnostics[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `diagnostics[].message` | `string` | yes | What is wrong, in one sentence. | | `diagnostics[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `diagnostics[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `diagnostics[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `diagnostics[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `stats` | `object` | yes | | | `stats.items` | `integer` | yes | Boxes, tables, rows, slices, places, entries, or commits written. | | `stats.links` | `integer` | yes | Arrows, relations, messages, flows, or merges written. | | `stats.groups` | `integer` | yes | Frames, sections, or folders written. | | `stats.dropped` | `integer` | yes | Things in the file left out by a cap or because the picture has no place for them; each is in diagnostics. | | `coverage` | `object \| null` | yes | How much of the file the picture shows; null when nothing was imported. When complete is false, the picture is not the whole file: say so. | | `validation` | `object \| null` | yes | The Plotwork validator run on `source`; null when nothing was imported. | | `parts` | `object[]` | yes | When the file is too big for one readable picture, the diagrams it was split into, in order, at most 24 a page; `source` is the first diagram of all. Show or draw each of them. Empty when the import is one diagram. | | `parts[].title` | `string` | yes | What this diagram shows, such as one resource group or one tag of an API. | | `parts[].kind` | `string` | yes | The kind of diagram. | | `parts[].source` | `string` | yes | The Plotwork source of this diagram; draw it with draw_diagram. | | `parts[].items` | `integer` | yes | The boxes, rows, or entries it draws. | | `parts[].valid` | `boolean` | yes | True when the source has no Plotwork errors. | | `parts[].warnings` | `Diagnostic[]` | yes | Plotwork quality warnings of this diagram. Lines and columns point into its source. | | `parts[].warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `parts[].warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `parts[].warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `parts[].warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `parts[].warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `parts[].warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `parts[].warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `partsTotal` | `integer` | yes | How many diagrams the import was split into in all; 0 when it is one diagram. | | `partsPage` | `integer` | yes | The page of parts this answer holds, from 1. | | `partsPages` | `integer` | yes | How many pages of parts there are; call again with partsPage for the next one. | | `outline` | `string[]` | yes | The diagram as plain text, to check it says what the file says; empty when nothing was imported. | | `outlineTruncated` | `boolean` | yes | True when the outline was cut short at its summary length. | | `viewUrl` | `string` | no | A link that shows a valid diagram in a browser; give it to the person who wants to look. The source is in the part after #, never sent to a server, but anyone with the link can read it. Absent when too long; next says so. | | `pngUrl` | `string` | no | The viewUrl with &download=png: opening it in a browser saves the diagram as a PNG for Slack, email, or a wiki. The PNG is drawn in the person's browser, not on a server, so it is a link to open, not an image address to embed. Absent when too long. | | `svgUrl` | `string` | no | The viewUrl with &download=svg: a person opens it to download the SVG. Not a file address for curl; use saveCommand. Absent when too long. | | `saveCommand` | `string` | no | Run in a shell to save the SVG into the repository: the CLI decodes the link locally, fetches nothing, and embeds the source. Set --out to the path; add --theme dark for dark. Absent when too long. | | `next` | `string` | yes | The next step in one sentence. | ## Behavior hints | Hint | Value | |---|---| | `readOnlyHint` | true | | `destructiveHint` | false | | `idempotentHint` | true | | `openWorldHint` | false | ## Bounds A bound that is reached is reported in the result; nothing is cut silently. | Bound | Value | What it limits | |---|---|---| | `maxImportLength` | 10000000 | Characters an import reads, not counting white space or lines that are only a comment. A longer text is answered with I102. | | `maxImportLocalLength` | 100000000 | Characters of text in one call on the local server, which runs on your own machine. A guard on memory, not a cap a real project meets. | | `maxImportTextLength` | 10485760 | Characters of text in one hosted call, comments and white space included, all files together. | | `maxImportFiles` | 200 | Files in one hosted call. | | `maxImportLocalFiles` | 20000 | Files in one call on the local server. | | `maxImportParts` | 24 | Diagrams one import is split into when the file is too big for one picture; each is validated. | | `maxImportNodes` | 40 | Boxes in an imported flow; the rest are left out with an I200 diagnostic. | | `maxImportTables` | 20 | Tables in an imported schema. | | `maxImportRows` | 20 | Rows of an imported matrix: endpoints, or rows of a table. | | `maxImportColumns` | 8 | Columns of an imported matrix: roles, scopes, or schemes. | | `maxImportPoints` | 60 | Places, slices, flows, or tiles in an imported chart. | | `maxImportSeries` | 6 | Series in an imported XY chart. | | `maxImportEntries` | 60 | Rows of an imported tree; a folder past it ends with an omitted line. | | `maxImportCommits` | 40 | Commits of an imported git graph, the newest kept, and events of an imported timeline. | | `maxImportMessages` | 30 | Messages of an imported sequence. | ## See also | Doc | Why | |---|---| | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Every tool, with the workflows first | | [`POST /v1/import`](https://docs.plotwork.dev/api/post-v1-import.md) | The same operation over HTTP | | [Language reference](https://docs.plotwork.dev/reference/language.md) | What the diagram source may contain | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop in practice | --- # map_icons > Page: https://docs.plotwork.dev/mcp/tools/map-icons. Index of every page: https://docs.plotwork.dev/llms.txt Finds an icon for each of up to 40 service or product names in one call, such as the services of an architecture. It is a workflow of the MCP server and the HTTP API, with the title "Map icons". ## TL;DR | Question | Answer | |---|---| | What is it? | A workflow: one call does the work of several tools and returns one combined answer. | | What does it do? | Finds an icon for each of up to 40 service or product names in one call, such as the services of an architecture. | | What does it replace? | up to 40 search_icons calls. | | How do I call it over HTTP? | [`POST /v1/icons/map`](https://docs.plotwork.dev/api/post-v1-icons-map.md) | | Does it change anything? | No. It is read-only and idempotent, and it talks to nothing outside itself. It saves no file and returns its answer as data. | | Which bounds apply? | `maxIconNames`, `maxIconNameLength`, `maxIconAlternatives`, `maxIconCandidates`. See the bounds below. | | Where do I read its schema as data? | An MCP client gets the name, description, and JSON Schema of every tool from `tools/list`. | ## What it does Workflow. Finds an icon for each of up to 40 service or product names in one call, such as the services of an architecture. For every name it returns the best provider:name icon with a confidence (exact, strong, or ambiguous) and up to 3 alternatives. It never guesses: a name that could mean several icons (a kind of service several clouds offer, or a match by idea only) comes back with resolved false and candidates to choose from, and a name that matches nothing is listed in gaps with a neutral fallback icon. Set provider to the cloud the architecture runs on, so "queue" finds that cloud's product first. Reads the bundled icons and, when hosted, the organisation icons (org:name). Replaces up to 40 search_icons calls. ## Steps | Operation | Runs | What it does | |---|---|---| | `searchIcons` | always | Search the bundled icons for a name or an idea. | One call performs at most 80 operations. ## Example call The arguments of the call, as JSON: ```json { "names": [ "S3", "Postgres", "Lambda", "load balancer", "queue" ], "provider": "aws" } ``` ## Prompts that lead to it - Which icons should I use for S3, Lambda, Postgres and a queue on AWS? - Pick icons for every service in this architecture. ## Arguments | Field | Type | Required | Description | |---|---|---|---| | `names` | `string[]` | yes | Services, products, or ideas to find icons for, written as the user wrote them ("S3", "Postgres", "load balancer"). Up to 40 names of up to 80 characters. | | `provider` | `"aws" \| "gcp" \| "azure" \| "alibaba" \| "oci" \| "ibm" \| "cloudflare" \| "digitalocean" \| "vercel"` | no | The cloud the architecture runs on. A name is looked up there first, so "queue" finds aws:sqs for aws. Omit it when the names are not tied to one provider. | ## Result | Field | Type | Required | Description | |---|---|---|---| | `provider` | `"aws" \| "gcp" \| "azure" \| "alibaba" \| "oci" \| "ibm" \| "cloudflare" \| "digitalocean" \| "vercel" \| null` | yes | The provider hint that was used, or null. | | `results` | `object[]` | yes | One entry per name that matched something, in the order sent. | | `results[].name` | `string` | yes | The name exactly as it was sent. | | `results[].resolved` | `boolean` | yes | True when icon holds the one icon to use. False when the name could mean more than one: choose from candidates, or ask the user. | | `results[].icon` | `string \| null` | yes | The icon to write as icon=, such as aws:lambda; null when resolved is false. | | `results[].confidence` | `"exact" \| "strong" \| "ambiguous"` | yes | exact: the name is the icon or product name. strong: the name clearly points at this icon and no other provider competes. ambiguous: several icons fit, or the match is by idea only. | | `results[].alternatives` | `string[]` | yes | Up to 3 other icons that also fit a resolved name, best first. Empty when none. | | `results[].candidates` | `string[]` | yes | When resolved is false, the icons that fit, best first (up to 4). Empty when resolved. | | `gaps` | `object[]` | yes | Names that matched nothing, in the order sent. | | `gaps[].name` | `string` | yes | The name that matched no icon. | | `gaps[].fallback` | `string` | yes | A neutral icon to use instead, or leave icon= out so the node type draws its own. | | `gaps[].reason` | `string` | yes | Why there is no match, and what to try. | | `resolvedCount` | `integer` | yes | How many names have one icon to use. | | `ambiguousCount` | `integer` | yes | How many names need a choice from candidates. | | `gapCount` | `integer` | yes | How many names matched nothing. | ## Behavior hints | Hint | Value | |---|---| | `readOnlyHint` | true | | `destructiveHint` | false | | `idempotentHint` | true | | `openWorldHint` | false | ## Bounds A bound that is reached is reported in the result; nothing is cut silently. | Bound | Value | What it limits | |---|---|---| | `maxIconNames` | 40 | Names in one call. | | `maxIconNameLength` | 80 | Characters in one name. | | `maxIconAlternatives` | 3 | Other icons listed beside a chosen icon. | | `maxIconCandidates` | 4 | Icons listed for a name that could mean several. | ## See also | Doc | Why | |---|---| | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Every tool, with the workflows first | | [`POST /v1/icons/map`](https://docs.plotwork.dev/api/post-v1-icons-map.md) | The same operation over HTTP | | [Language reference](https://docs.plotwork.dev/reference/language.md) | What the diagram source may contain | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop in practice | --- # sync_markdown > Page: https://docs.plotwork.dev/mcp/tools/sync-markdown. Index of every page: https://docs.plotwork.dev/llms.txt Turns the diagram fences of a Markdown document into pictures, as data. It is a workflow of the MCP server and the HTTP API, with the title "Sync Markdown". ## TL;DR | Question | Answer | |---|---| | What is it? | A workflow: one call does the work of several tools and returns one combined answer. | | What does it do? | Turns the diagram fences of a Markdown document into pictures, as data. | | What does it replace? | convert, validate, render and sync steps for every fence in a document. | | How do I call it over HTTP? | [`POST /v1/markdown/sync`](https://docs.plotwork.dev/api/post-v1-markdown-sync.md) | | Does it change anything? | No. It is read-only and idempotent, and it talks to nothing outside itself. It saves no file and returns its answer as data. | | Which bounds apply? | `maxMarkdownLength`, `maxDiagrams`, `maxOutputBytes`, `maxPathLength`, `maxPrefixLength`, `maxSyncPageSize`. See the bounds below. | | Where do I read its schema as data? | An MCP client gets the name, description, and JSON Schema of every tool from `tools/list`. | ## What it does Workflow. Turns the diagram fences of a Markdown document into pictures, as data. Send the whole document and its path. Returns the rewritten document (each fence folded into a "Diagram source" section with the picture and a text description, the rest byte for byte unchanged), the light and dark SVG files to write (paths from the repository root), and a report per fence with errors at lines of your document. convertMermaid true first turns each convertible mermaid fence into a diagram fence; one that cannot convert or has errors is left as it was and reported. Then write markdown to documentPath and each file to its path, and review the diff. mode "check" only reports. The SVGs can be large: files "none" returns each file's path, bytes, sha256, and viewUrl instead, and page with pageSize returns the files a page at a time. Writes no file itself. Limits: 10000000 characters, 30 diagrams, 2000000 bytes of output; caps says when one stopped work. Replaces the convert, validate, render and sync steps for every fence in a document. ## Steps | Operation | Runs | What it does | |---|---|---| | `convertMermaidFences` | only when needed | Replace every convertible mermaid fence of a Markdown document with a diagram fence. | | `renderPair` | always | Draw one diagram in the light and the dark theme with its text description. | One call performs at most 31 operations. ## Example call The arguments of the call, as JSON: ````json { "markdown": "# Login\n\n```diagram\nflow \"Login\"\nnode user person \"User\"\nnode api service \"Auth API\"\nuser -> api : \"POST /login\"\n```\n", "documentPath": "docs/login.md", "convertMermaid": true } ```` The `markdown` argument, written out one line at a time: ````md # Login ```diagram flow "Login" node user person "User" node api service "Auth API" user -> api : "POST /login" ``` ```` ## Prompts that lead to it - Replace the Mermaid diagrams in README.md with Plotwork diagrams. - Render the diagram fences in docs/login.md and give me the files to write. ## Arguments | Field | Type | Required | Description | |---|---|---|---| | `markdown` | `string` | yes | The whole Markdown document, exactly as it is on disk, with its line endings. Up to 10000000 characters. at least 1 character; at most 10000000 characters. | | `documentPath` | `string` | yes | Where the document lives, relative to the repository root, with forward slashes, for example docs/guide.md. Picture links and file paths are worked out from it. at least 1 character; at most 300 characters. | | `assetsDir` | `string` | no | The directory for the SVG files, relative to the repository root. The default is assets/diagrams next to the document. at least 1 character; at most 300 characters. | | `prefix` | `string` | no | Text put in front of every picture file name, for documents that share one assets directory. By default a titled diagram is named from its title and an untitled one from the document name. at least 1 character; at most 32 characters. | | `convertMermaid` | `boolean` | no | When true, every ```mermaid fence that can be converted is replaced by a ```diagram fence first, then drawn. Fences that cannot be converted are left exactly as they were and reported. default false. | | `mode` | `"sync" \| "check"` | no | sync (the default) returns the rewritten document and the files to write. check changes nothing: it only reports whether a picture is stale or a fence would convert. default "sync". | | `files` | `"inline" \| "none"` | no | inline (the default) returns each SVG file with its content. none returns each with path, bytes, sha256, and viewUrl instead, so the answer stays small. default "inline". | | `page` | `integer` | no | Which page of files to return, from 1; pictures are paged in document order, light and dark together. minimum 1. | | `pageSize` | `integer` | no | Pictures per page, at most 30. Setting page or pageSize turns paging on. minimum 1; maximum 30. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## Result | Field | Type | Required | Description | |---|---|---|---| | `mode` | `"sync" \| "check"` | yes | The mode that ran. | | `markdown` | `string` | yes | The document to write back: every diagram fence wrapped in its collapsed source section, with the picture block and text description under it, and everything else unchanged. In check mode this is the document that was sent. | | `changed` | `boolean` | yes | True when the document or a file differs from what was sent. In check mode this says the pictures are stale or a fence would convert. | | `files` | `object[]` | yes | The SVG files to write, light and dark for each picture that was refreshed, with paths relative to the repository root. Empty in check mode. A picture that was already current has no files. | | `files[].path` | `string` | yes | Where to write the file, relative to the repository root. | | `files[].content` | `string` | no | The file contents; absent when files is none. | | `files[].encoding` | `"utf8"` | yes | How content is encoded. Always utf8: the files are SVG text. | | `files[].bytes` | `integer` | no | Only when files is none: the size of the file in bytes. | | `files[].sha256` | `string` | no | Only when files is none: the SHA-256 of the content, in hex, to check a file fetched later. | | `files[].viewUrl` | `string` | no | Only when files is none: a link that shows this diagram in a browser. | | `report` | `object[]` | yes | One entry per fence handled, in document order. | | `report[].id` | `string \| null` | yes | The picture id, which names the SVG files; null for a fence that has none (a mermaid fence that was not converted). | | `report[].source` | `"diagram" \| "mermaid"` | yes | diagram for a fence that was already Plotwork, mermaid for one that came from Mermaid. | | `report[].kind` | `string \| null` | yes | The diagram kind, or null when it could not be read. | | `report[].line` | `integer` | yes | The 1-based line of the opening fence in the document that was sent. minimum 1. | | `report[].status` | `"synced" \| "converted" \| "unchanged" \| "skipped" \| "failed"` | yes | synced: the picture was written or refreshed. converted: a mermaid fence became a diagram fence and was drawn. unchanged: the picture was already current. skipped: a cap stopped it, see message. failed: it has errors, the fence is left as it was. | | `report[].diagnostics` | `Diagnostic[]` | yes | Errors and warnings, with line numbers counted in the document that was sent. For a converted fence they point at the Mermaid lines. | | `report[].diagnostics[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `report[].diagnostics[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `report[].diagnostics[].message` | `string` | yes | What is wrong, in one sentence. | | `report[].diagnostics[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `report[].diagnostics[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `report[].diagnostics[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `report[].diagnostics[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `report[].message` | `string \| null` | yes | A plain sentence when something needs saying, such as why a fence was skipped; otherwise null. | | `summary` | `object` | yes | | | `summary.diagrams` | `integer` | yes | Fences handled: synced, converted, unchanged, skipped, and failed together. | | `summary.synced` | `integer` | yes | | | `summary.converted` | `integer` | yes | | | `summary.unchanged` | `integer` | yes | | | `summary.skipped` | `integer` | yes | | | `summary.failed` | `integer` | yes | | | `summary.mermaidIgnored` | `integer` | yes | Mermaid fences left alone because convertMermaid was false. | | `summary.files` | `integer` | yes | Files to write. | | `summary.outputBytes` | `integer` | yes | Bytes in the returned document and files together. | | `caps` | `object` | yes | | | `caps.hit` | `boolean` | yes | True when a cap stopped some of the work. Nothing is cut silently: see messages. | | `caps.messages` | `string[]` | yes | One plain sentence per cap that was hit, saying what was skipped and what to do. | | `caps.maxDiagrams` | `integer` | yes | Most fences handled in one call. | | `caps.maxOutputBytes` | `integer` | yes | Most bytes in the returned document and files together. | | `pages` | `object` | no | Present when page or pageSize was sent: which pictures files holds, and the next page. | | `pages.page` | `integer` | yes | | | `pages.pageSize` | `integer` | yes | | | `pages.pages` | `integer` | yes | Pages in all. | | `pages.pictures` | `integer` | yes | Pictures in all, each a light and a dark file. | | `pages.nextPage` | `integer \| null` | yes | The page to ask for next, or null on the last one. | | `next` | `string` | yes | The next step in one sentence. | ## Behavior hints | Hint | Value | |---|---| | `readOnlyHint` | true | | `destructiveHint` | false | | `idempotentHint` | true | | `openWorldHint` | false | ## Bounds A bound that is reached is reported in the result; nothing is cut silently. | Bound | Value | What it limits | |---|---|---| | `maxMarkdownLength` | 10000000 | Characters in the document. | | `maxDiagrams` | 30 | Diagram fences, plus mermaid fences when converting, handled in one call; the rest are left alone and reported. | | `maxOutputBytes` | 2000000 | Bytes in the returned document and files together; fences that would pass it are not drawn and are reported. | | `maxPathLength` | 300 | Characters in the document path or the assets directory. | | `maxPrefixLength` | 32 | Characters in the file name prefix. | | `maxSyncPageSize` | 30 | Pictures in one page of files, with page and pageSize. | ## See also | Doc | Why | |---|---| | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Every tool, with the workflows first | | [`POST /v1/markdown/sync`](https://docs.plotwork.dev/api/post-v1-markdown-sync.md) | The same operation over HTTP | | [Language reference](https://docs.plotwork.dev/reference/language.md) | What the diagram source may contain | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop in practice | --- # suggest_diagram > Page: https://docs.plotwork.dev/mcp/tools/suggest-diagram. Index of every page: https://docs.plotwork.dev/llms.txt Picks the kind of diagram for a request in plain words, such as "permission matrix for these endpoints and roles" or "where our cloud spend goes", with optional pasted data (a table, a list, numbers by period). It is a workflow of the MCP server and the HTTP API, with the title "Suggest diagram". ## TL;DR | Question | Answer | |---|---| | What is it? | A workflow: one call does the work of several tools and returns one combined answer. | | What does it do? | Picks the kind of diagram for a request in plain words, such as "permission matrix for these endpoints and roles" or "where our cloud spend goes", with optional pasted data (a table, a list, numbers by period). | | What does it replace? | reading every kind in get_diagram_syntax to choose one. | | How do I call it over HTTP? | [`POST /v1/diagrams/suggest`](https://docs.plotwork.dev/api/post-v1-diagrams-suggest.md) | | Does it change anything? | No. It is read-only and idempotent, and it talks to nothing outside itself. It saves no file and returns its answer as data. | | Which bounds apply? | `maxRequestLength`, `maxSuggestDataLength`, `maxSuggestions`, `maxStarterItems`. See the bounds below. | | Where do I read its schema as data? | An MCP client gets the name, description, and JSON Schema of every tool from `tools/list`. | ## What it does Workflow. Picks the kind of diagram for a request in plain words, such as "permission matrix for these endpoints and roles" or "where our cloud spend goes", with optional pasted data (a table, a list, numbers by period). Returns up to 3 kinds, best first, each with a one-sentence reason, a confidence, the statements to use, and a short starter source that parses, built from the names and numbers in the request and data. Call it when you are unsure which kind fits, then edit the starter and call draw_diagram. Replaces reading every kind in get_diagram_syntax to choose one. ## Steps | Operation | Runs | What it does | |---|---|---| | `parse` | only when needed | Parse the source and return its errors, without laying it out or linting it. | One call performs at most 3 operations. ## Example call The arguments of the call, as JSON: ```json { "request": "Permission matrix for these endpoints and roles", "data": "Roles: Admin, Editor, Viewer\nEndpoints: GET /posts, POST /posts, DELETE /posts" } ``` The `data` argument, written out one line at a time: ```text Roles: Admin, Editor, Viewer Endpoints: GET /posts, POST /posts, DELETE /posts ``` ## Prompts that lead to it - Which diagram should I use to show who can call which endpoint? - What is the best way to draw where our cloud spend goes? - Suggest a diagram for comparing three message brokers. ## Arguments | Field | Type | Required | Description | |---|---|---|---| | `request` | `string` | yes | What the picture is for, in plain words, such as "permission matrix for these endpoints and roles" or "how a refund moves between support and billing". at least 1 character; at most 1000 characters. | | `data` | `string` | no | Optional text to draw from: a pasted table, a list, numbers by period, or notes. Names and numbers in it are used in the starters. at most 20000 characters. | ## Result | Field | Type | Required | Description | |---|---|---|---| | `request` | `string` | yes | The request that was read. | | `suggestions` | `object[]` | yes | Up to 3, best first, one for each kind and style. | | `suggestions[].kind` | `string` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `suggestions[].style` | `string \| null` | yes | The style the starter uses: compare, score, raci, or risk for a matrix, files or org for a tree; null for the kind's only style. | | `suggestions[].why` | `string` | yes | One sentence: why this kind fits. | | `suggestions[].confidence` | `"high" \| "medium" \| "low"` | yes | high: the text names this kind of picture or its shape fits it exactly. medium: it leans toward it. low: a fallback worth considering. | | `suggestions[].starter` | `string` | yes | A short Plotwork source of this kind that parses without errors: built from the names and numbers in the text where it could find them, otherwise a small example to edit. Replace every placeholder before you draw it. | | `suggestions[].tailored` | `boolean` | yes | True when the starter uses names or numbers from the text; false when it is the kind's small example. | | `suggestions[].statements` | `string[]` | yes | The few statements of this kind to write, as syntax. get_diagram_syntax with this kind has them all. | | `importFormat` | `string` | no | Present when the data is, or the request names, a file import_source reads (sql, openapi, compose, terraform, and so on): call import_source with this format and the file's text instead of editing a starter. | | `next` | `string` | yes | The next step in one sentence. | ## Behavior hints | Hint | Value | |---|---| | `readOnlyHint` | true | | `destructiveHint` | false | | `idempotentHint` | true | | `openWorldHint` | false | ## Bounds A bound that is reached is reported in the result; nothing is cut silently. | Bound | Value | What it limits | |---|---|---| | `maxRequestLength` | 1000 | Characters in the request. | | `maxSuggestDataLength` | 20000 | Characters in the pasted data. | | `maxSuggestions` | 3 | Kinds suggested in one answer. | | `maxStarterItems` | 8 | Items, rows, or columns a starter takes from the text; plan_document_diagrams takes the same. | ## See also | Doc | Why | |---|---| | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Every tool, with the workflows first | | [`POST /v1/diagrams/suggest`](https://docs.plotwork.dev/api/post-v1-diagrams-suggest.md) | The same operation over HTTP | | [Language reference](https://docs.plotwork.dev/reference/language.md) | What the diagram source may contain | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop in practice | --- # plan_document_diagrams > Page: https://docs.plotwork.dev/mcp/tools/plan-document-diagrams. Index of every page: https://docs.plotwork.dev/llms.txt Reads a whole document (Markdown, HTML, or plain text pasted from Confluence, Notion, or Google Docs), splits it by its headings, and returns the sections a picture would help: the heading and its line, the kind, a one-sentence reason, a confidence, and a starter source drafted from the section's own lists, tables, numbers, and dates. It is a workflow of the MCP server and the HTTP API, with the title "Plan document diagrams". ## TL;DR | Question | Answer | |---|---| | What is it? | A workflow: one call does the work of several tools and returns one combined answer. | | What does it do? | Reads a whole document (Markdown, HTML, or plain text pasted from Confluence, Notion, or Google Docs), splits it by its headings, and returns the sections a picture would help: the heading and its line, the kind, a one-sentence reason, a confidence, and a starter source drafted from the section's own lists, tables, numbers, and dates. | | What does it replace? | reading the document section by section and choosing a kind for each. | | How do I call it over HTTP? | [`POST /v1/markdown/plan`](https://docs.plotwork.dev/api/post-v1-markdown-plan.md) | | Does it change anything? | No. It is read-only and idempotent, and it talks to nothing outside itself. It saves no file and returns its answer as data. | | Which bounds apply? | `maxPlanDocumentLength`, `maxPlanSections`, `maxPlanSuggestions`. See the bounds below. | | Where do I read its schema as data? | An MCP client gets the name, description, and JSON Schema of every tool from `tools/list`. | ## What it does Workflow. Reads a whole document (Markdown, HTML, or plain text pasted from Confluence, Notion, or Google Docs), splits it by its headings, and returns the sections a picture would help: the heading and its line, the kind, a one-sentence reason, a confidence, and a starter source drafted from the section's own lists, tables, numbers, and dates. Sections that read well as text, or already have a diagram, are left out. Draws nothing and changes nothing. Replaces reading the document section by section and choosing a kind for each. ## Steps | Operation | Runs | What it does | |---|---|---| | `parse` | only when needed | Parse the source and return its errors, without laying it out or linting it. | One call performs at most 12 operations. ## Example call The arguments of the call, as JSON: ```json { "document": "# Refunds\n\nHow a refund moves through the company.\n\n## Process\n\nCustomer: asks for a refund\nSupport: checks the order\nBilling: issues the refund\nCustomer: gets the money back\n\n## Who does what\n\n| Task | Support | Billing | Finance |\n|---|---|---|---|\n| Approve | A | R | I |\n| Pay out | I | A R | C |\n" } ``` The `document` argument, written out one line at a time: ```text # Refunds How a refund moves through the company. ## Process Customer: asks for a refund Support: checks the order Billing: issues the refund Customer: gets the money back ## Who does what | Task | Support | Billing | Finance | |---|---|---|---| | Approve | A | R | I | | Pay out | I | A R | C | ``` ## Prompts that lead to it - Which parts of this design doc need a diagram? - Add pictures to this Confluence page. - Plan the diagrams for our onboarding guide. ## Arguments | Field | Type | Required | Description | |---|---|---|---| | `document` | `string` | yes | The whole document: Markdown, plain text pasted from Confluence, Notion, or Google Docs, or HTML. Up to 10000000 characters. at least 1 character; at most 10000000 characters. | | `maxSuggestions` | `integer` | no | Most sections to suggest a picture for, up to 12. The strongest are kept, in document order. The default is 8. minimum 1; maximum 12; default 8. | ## Result | Field | Type | Required | Description | |---|---|---|---| | `summary` | `string` | yes | One sentence: how many sections were read and how many would gain a picture. | | `format` | `"markdown" \| "html" \| "text"` | yes | How the document was read: Markdown headings, HTML h1 to h6, or plain text with heading-like lines. | | `sectionsRead` | `integer` | yes | Sections read, up to the cap. | | `suggestions` | `object[]` | yes | In document order. Sections that read well as text, or already have a diagram, are not listed. | | `suggestions[].heading` | `string` | yes | The heading of the section, as written. | | `suggestions[].line` | `integer` | yes | The 1-based line of the heading in the document that was sent. minimum 1. | | `suggestions[].kind` | `string` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `suggestions[].style` | `string \| null` | yes | The style the starter uses: compare, score, raci, or risk for a matrix, files or org for a tree; null for the kind's only style. | | `suggestions[].why` | `string` | yes | One sentence: what in the section points to this kind. | | `suggestions[].confidence` | `"high" \| "medium" \| "low"` | yes | high: the text names this kind of picture or its shape fits it exactly. medium: it leans toward it. low: a fallback worth considering. | | `suggestions[].starter` | `string` | yes | A short Plotwork source of this kind that parses without errors: built from the names and numbers in the text where it could find them, otherwise a small example to edit. Replace every placeholder before you draw it. | | `suggestions[].tailored` | `boolean` | yes | True when the starter uses names or numbers from the text; false when it is the kind's small example. | | `caps` | `object` | yes | | | `caps.hit` | `boolean` | yes | True when a cap left something out; messages says what. | | `caps.messages` | `string[]` | yes | | | `caps.maxSections` | `integer` | yes | | | `caps.maxSuggestions` | `integer` | yes | | | `next` | `string` | yes | The next step in one sentence. | ## Behavior hints | Hint | Value | |---|---| | `readOnlyHint` | true | | `destructiveHint` | false | | `idempotentHint` | true | | `openWorldHint` | false | ## Bounds A bound that is reached is reported in the result; nothing is cut silently. | Bound | Value | What it limits | |---|---|---| | `maxPlanDocumentLength` | 10000000 | Characters in the document. | | `maxPlanSections` | 200 | Sections read; a document with more says so in caps. | | `maxPlanSuggestions` | 12 | Sections suggested in one answer, the strongest first; the rest are counted in caps. | ## See also | Doc | Why | |---|---| | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Every tool, with the workflows first | | [`POST /v1/markdown/plan`](https://docs.plotwork.dev/api/post-v1-markdown-plan.md) | The same operation over HTTP | | [Language reference](https://docs.plotwork.dev/reference/language.md) | What the diagram source may contain | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop in practice | --- # find_pattern > Page: https://docs.plotwork.dev/mcp/tools/find-pattern. Index of every page: https://docs.plotwork.dev/llms.txt Finds a ready-made, checked architecture diagram for a request such as "three tier network", "serverless api", or "two availability zones". It is a workflow of the MCP server and the HTTP API, with the title "Find pattern". ## TL;DR | Question | Answer | |---|---| | What is it? | A workflow: one call does the work of several tools and returns one combined answer. | | What does it do? | Finds a ready-made, checked architecture diagram for a request such as "three tier network", "serverless api", or "two availability zones". | | What does it replace? | searching the documentation for an example to copy. | | How do I call it over HTTP? | [`POST /v1/patterns/find`](https://docs.plotwork.dev/api/post-v1-patterns-find.md) | | Does it change anything? | No. It is read-only and idempotent, and it talks to nothing outside itself. It saves no file and returns its answer as data. | | Which bounds apply? | `maxPatternQueryLength`, `maxPatternTags`, `maxPatternResults`. See the bounds below. | | Where do I read its schema as data? | An MCP client gets the name, description, and JSON Schema of every tool from `tools/list`. | ## What it does Workflow. Finds a ready-made, checked architecture diagram for a request such as "three tier network", "serverless api", or "two availability zones". Send the request in plain words, and optionally a provider (neutral, aws, azure, or gcp), tags, a diagram kind, and a limit of up to 5. Returns the best matches first, each with an id, a one-sentence summary, tags, and the flavours it comes in, never the source. Start here when the user asks for a common architecture: a pattern is already validated, laid out, and described, so you adapt it instead of drawing from nothing. Replaces searching the documentation for an example to copy. ## Steps | Operation | Runs | What it does | |---|---|---| | `findPatterns` | always | Search the pattern library for an architecture by its words, with optional provider, tag, and kind filters. | One call performs at most 1 operations. ## Example call The arguments of the call, as JSON: ```json { "query": "three tier network", "provider": "azure" } ``` ## Prompts that lead to it - Draw a three-tier network with load balancers. - Which pattern shows a primary and a replica database in two availability zones? - Find a serverless API pattern for AWS. ## Arguments | Field | Type | Required | Description | |---|---|---|---| | `query` | `string` | yes | The architecture wanted, in plain words, such as "three tier network". at least 1 character; at most 200 characters. | | `provider` | `"neutral" \| "aws" \| "azure" \| "gcp"` | no | Only patterns that come in this flavour: neutral, aws, azure, or gcp. | | `tags` | `string[]` | no | Tags every result must have, such as "load balancer". Up to 5. | | `kind` | `"flow" \| "sequence" \| "schema" \| "git" \| "gantt" \| "class" \| "state" \| "timeline" \| "journey" \| "mindmap" \| "treemap" \| "pie" \| "quadrant" \| "layers" \| "c4" \| "matrix" \| "sankey" \| "xychart" \| "tree" \| "swimlane"` | no | Only patterns of this diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane. | | `limit` | `integer` | no | Most results to return, up to 5. minimum 1; maximum 5; default 5. | ## Result | Field | Type | Required | Description | |---|---|---|---| | `query` | `string` | yes | The query that was searched. | | `matches` | `object[]` | yes | Up to the limit, best match first. Empty when nothing fits. | | `matches[].id` | `string` | yes | The pattern id, as find_pattern returns it, such as three-tier-network. at least 1 character; at most 60 characters. | | `matches[].title` | `string` | yes | | | `matches[].summary` | `string` | yes | One sentence: what the diagram shows. | | `matches[].kind` | `string` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `matches[].tags` | `string[]` | yes | | | `matches[].providers` | `("neutral" \| "aws" \| "azure" \| "gcp")[]` | yes | The flavours the pattern comes in. | | `matches[].score` | `number` | yes | How well it matches; only the order of the results is meaningful. | | `next` | `string` | yes | The next step in one sentence. | ## Behavior hints | Hint | Value | |---|---| | `readOnlyHint` | true | | `destructiveHint` | false | | `idempotentHint` | true | | `openWorldHint` | false | ## Bounds A bound that is reached is reported in the result; nothing is cut silently. | Bound | Value | What it limits | |---|---|---| | `maxPatternQueryLength` | 200 | Characters in the query. | | `maxPatternTags` | 5 | Tags a search can require. | | `maxPatternResults` | 5 | Matches in one answer. | ## See also | Doc | Why | |---|---| | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Every tool, with the workflows first | | [`POST /v1/patterns/find`](https://docs.plotwork.dev/api/post-v1-patterns-find.md) | The same operation over HTTP | | [Language reference](https://docs.plotwork.dev/reference/language.md) | What the diagram source may contain | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop in practice | --- # get_pattern > Page: https://docs.plotwork.dev/mcp/tools/get-pattern. Index of every page: https://docs.plotwork.dev/llms.txt Returns one pattern filled for a flavour: its valid Plotwork source, every slot with its meaning and value, the advice on what to keep, rename, and split, and the structure as groups, overlays, and nodes. It is a workflow of the MCP server and the HTTP API, with the title "Get pattern". ## TL;DR | Question | Answer | |---|---| | What is it? | A workflow: one call does the work of several tools and returns one combined answer. | | What does it do? | Returns one pattern filled for a flavour: its valid Plotwork source, every slot with its meaning and value, the advice on what to keep, rename, and split, and the structure as groups, overlays, and nodes. | | What does it replace? | searching for an example and copying it by hand. | | How do I call it over HTTP? | [`POST /v1/patterns/get`](https://docs.plotwork.dev/api/post-v1-patterns-get.md) | | Does it change anything? | No. It is read-only and idempotent, and it talks to nothing outside itself. It saves no file and returns its answer as data. | | Which bounds apply? | `maxPatternSlots`. See the bounds below. | | Where do I read its schema as data? | An MCP client gets the name, description, and JSON Schema of every tool from `tools/list`. | ## What it does Workflow. Returns one pattern filled for a flavour: its valid Plotwork source, every slot with its meaning and value, the advice on what to keep, rename, and split, and the structure as groups, overlays, and nodes. Send the id from find_pattern, a provider (neutral by default, or aws, azure, gcp), and optionally slot values such as {"office": "Branch office"} to name its parts. Edit the source for anything a slot does not cover, then validate it. An unknown id, flavour, or slot, or a value with a quote in it, comes back as a problem with a fix, not as an error. Replaces searching for an example and copying it by hand. ## Steps | Operation | Runs | What it does | |---|---|---| | `fillPattern` | always | Fill one pattern for one flavour with slot values: its source, slots, notes, group tree, and outline. | One call performs at most 1 operations. ## Example call The arguments of the call, as JSON: ```json { "id": "three-tier-network", "provider": "azure", "slots": { "office": "Branch office" } } ``` ## Prompts that lead to it - Show me the three-tier network pattern for Azure and rename the office network to Branch office. - What can I change in the two-zone network pattern? ## Arguments | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The pattern id, as find_pattern returns it, such as three-tier-network. at least 1 character; at most 60 characters. | | `provider` | `"neutral" \| "aws" \| "azure" \| "gcp"` | no | neutral (generic glyphs, no vendor names; the default), or aws, azure, or gcp for that provider's icons. Not every pattern comes in every flavour; the answer lists the ones it has. default "neutral". | | `slots` | `object` | no | Slot values keyed by slot id, such as {"office": "Branch office"}: plain text of up to 60 characters, no quotes or line breaks. Omit a slot to keep its default. get_pattern lists the slots and what they mean. | ## Result | Field | Type | Required | Description | |---|---|---|---| | `found` | `boolean` | yes | True when the pattern and flavour exist and the slot values are usable. When false, problems says why and nothing else is filled. | | `id` | `string` | yes | The pattern id that was asked for. | | `title` | `string \| null` | yes | | | `patternSummary` | `string \| null` | yes | One sentence: what the pattern shows. | | `whenToUse` | `string \| null` | yes | | | `kind` | `string \| null` | yes | | | `flavour` | `"neutral" \| "aws" \| "azure" \| "gcp" \| null` | yes | The flavour that was filled. | | `flavours` | `("neutral" \| "aws" \| "azure" \| "gcp")[]` | yes | Every flavour this pattern comes in; empty when the pattern was not found. | | `source` | `string \| null` | yes | Plotwork source, valid and ready to use; null when not found. | | `slots` | `object[]` | yes | Every slot the source uses, with the value it has. | | `slots[].id` | `string` | yes | The slot id; the key to use in slots. | | `slots[].value` | `string` | yes | The text the slot has in the source: the value sent, or the default. | | `slots[].default` | `string` | yes | The default text. | | `slots[].meaning` | `string` | yes | What belongs in this slot. | | `slots[].isDefault` | `boolean` | yes | True when the slot kept its default. | | `notes` | `object \| null` | yes | How to adapt the pattern: what to keep, what to rename, and when to split. | | `problems` | `object[]` | yes | Mistakes in the id, the flavour, or the slot values, each with a fix. Empty when found. | | `problems[].code` | `"unknown-pattern" \| "unknown-flavour" \| "unknown-slot" \| "bad-slot-value" \| "bad-source"` | yes | | | `problems[].message` | `string` | yes | | | `problems[].hint` | `string` | yes | How to fix it. | | `tree` | `object[]` | yes | The groups, overlays, and nodes as drawn, depth first, so you can see what to rename. | | `tree[].depth` | `integer` | yes | How deep the item sits: 0 for the top level. | | `tree[].kind` | `"group" \| "overlay" \| "node" \| "actor" \| "table"` | yes | | | `tree[].id` | `string` | yes | The id written in the source. | | `tree[].label` | `string` | yes | | | `tree[].type` | `string \| null` | yes | The group type, node type, or actor type; null for a table or an untyped group. | | `outline` | `string[]` | yes | The text description of the diagram. | | `next` | `string` | yes | The next step in one sentence. | ## Behavior hints | Hint | Value | |---|---| | `readOnlyHint` | true | | `destructiveHint` | false | | `idempotentHint` | true | | `openWorldHint` | false | ## Bounds A bound that is reached is reported in the result; nothing is cut silently. | Bound | Value | What it limits | |---|---|---| | `maxPatternSlots` | 40 | Slot values in one call; draw_from_pattern takes the same. | ## See also | Doc | Why | |---|---| | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Every tool, with the workflows first | | [`POST /v1/patterns/get`](https://docs.plotwork.dev/api/post-v1-patterns-get.md) | The same operation over HTTP | | [Language reference](https://docs.plotwork.dev/reference/language.md) | What the diagram source may contain | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop in practice | --- # draw_from_pattern > Page: https://docs.plotwork.dev/mcp/tools/draw-from-pattern. Index of every page: https://docs.plotwork.dev/llms.txt Fills a pattern and draws it in one call. It is a workflow of the MCP server and the HTTP API, with the title "Draw from pattern". ## TL;DR | Question | Answer | |---|---| | What is it? | A workflow: one call does the work of several tools and returns one combined answer. | | What does it do? | Fills a pattern and draws it in one call. | | What does it replace? | get_pattern, validate_diagram, outline_diagram and render_diagram calls. | | How do I call it over HTTP? | [`POST /v1/patterns/draw`](https://docs.plotwork.dev/api/post-v1-patterns-draw.md) | | Does it change anything? | No. It is read-only and idempotent, and it talks to nothing outside itself. It saves no file and returns its answer as data. | | Which bounds apply? | None beyond the limits of each argument, which are in the schema below. | | Where do I read its schema as data? | An MCP client gets the name, description, and JSON Schema of every tool from `tools/list`. | ## What it does Workflow. Fills a pattern and draws it in one call. Send the id from find_pattern, a provider (neutral by default, or aws, azure, gcp), optional slot values that rename its parts, and the themes. Returns the source, the slots, the advice on adapting it, the quality warnings, a plain-text outline, and the SVG for light, dark, or both. Use it when a pattern fits with only names changed; use get_pattern to edit the source first. An unknown id, flavour, or slot comes back as a problem with a fix. svg, picturesOmitted, viewUrl, pngUrl, saveCommand, and detail work as in draw_diagram. Replaces the get_pattern, validate_diagram, outline_diagram and render_diagram calls. ## Steps | Operation | Runs | What it does | |---|---|---| | `fillPattern` | always | Fill one pattern for one flavour with slot values: its source, slots, notes, group tree, and outline. | | `validate` | only when needed | Parse the source and lint it: every error, and the quality, icon, and group-type warnings. | | `outline` | only when needed | Describe a valid diagram as plain text. | | `render` | only when needed | Lay out and paint a valid diagram in one color scheme. | One call performs at most 5 operations. ## Example call The arguments of the call, as JSON: ```json { "id": "two-zone-network", "provider": "aws", "themes": "light" } ``` ## Prompts that lead to it - Draw a two-zone network on AWS with a replicated database. - Draw the web application pattern for Azure and call the API "Billing API". ## Arguments | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The pattern id, as find_pattern returns it, such as three-tier-network. at least 1 character; at most 60 characters. | | `provider` | `"neutral" \| "aws" \| "azure" \| "gcp"` | no | neutral (generic glyphs, no vendor names; the default), or aws, azure, or gcp for that provider's icons. Not every pattern comes in every flavour; the answer lists the ones it has. default "neutral". | | `slots` | `object` | no | Slot values keyed by slot id, such as {"office": "Branch office"}: plain text of up to 60 characters, no quotes or line breaks. Omit a slot to keep its default. get_pattern lists the slots and what they mean. | | `themes` | `"light" \| "dark" \| "both"` | no | light, dark, or both (the default): the light picture and the dark one a dark page needs. default "both". | | `detail` | `"summary" \| "full"` | no | summary (the default), or full: the whole outline, and the source embedded in each SVG. default "summary". | | `svg` | `"auto" \| "always" \| "never"` | no | auto (the default) returns the SVGs, but over MCP leaves them out when the answer would be too big for a client (picturesOmitted). always returns them at any size. never draws nothing, which is cheaper. The HTTP API treats auto as always. default "auto". | | `embedSource` | `boolean` | no | Write the source into each SVG so read_diagram can give it back. Set it when you save the SVG in a repository or publish it to edit later; anyone with the file or link can then read the source. default false. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## Result | Field | Type | Required | Description | |---|---|---|---| | `found` | `boolean` | yes | True when the pattern and flavour exist and the slot values are usable. When false, problems says why and nothing else is filled. | | `id` | `string` | yes | The pattern id that was asked for. | | `title` | `string \| null` | yes | | | `patternSummary` | `string \| null` | yes | One sentence: what the pattern shows. | | `whenToUse` | `string \| null` | yes | | | `kind` | `string \| null` | yes | | | `flavour` | `"neutral" \| "aws" \| "azure" \| "gcp" \| null` | yes | The flavour that was filled. | | `flavours` | `("neutral" \| "aws" \| "azure" \| "gcp")[]` | yes | Every flavour this pattern comes in; empty when the pattern was not found. | | `source` | `string \| null` | yes | Plotwork source, valid and ready to use; null when not found. | | `slots` | `object[]` | yes | Every slot the source uses, with the value it has. | | `slots[].id` | `string` | yes | The slot id; the key to use in slots. | | `slots[].value` | `string` | yes | The text the slot has in the source: the value sent, or the default. | | `slots[].default` | `string` | yes | The default text. | | `slots[].meaning` | `string` | yes | What belongs in this slot. | | `slots[].isDefault` | `boolean` | yes | True when the slot kept its default. | | `notes` | `object \| null` | yes | How to adapt the pattern: what to keep, what to rename, and when to split. | | `problems` | `object[]` | yes | Mistakes in the id, the flavour, or the slot values, each with a fix. Empty when found. | | `problems[].code` | `"unknown-pattern" \| "unknown-flavour" \| "unknown-slot" \| "bad-slot-value" \| "bad-source"` | yes | | | `problems[].message` | `string` | yes | | | `problems[].hint` | `string` | yes | How to fix it. | | `valid` | `boolean` | yes | True when the source has no errors. When false, nothing was drawn: fix the errors and call again. | | `errors` | `Diagnostic[]` | yes | Every error found in one pass, each with line, column, and a hint. Empty when valid. | | `errors[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `errors[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `errors[].message` | `string` | yes | What is wrong, in one sentence. | | `errors[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `errors[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `errors[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `errors[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `warnings` | `Diagnostic[]` | yes | Quality warnings for a valid diagram: too dense, too wide, text cut, unconnected nodes, and suggestions for an icon or a group type. Empty when invalid. | | `warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `summary` | `string \| null` | yes | One-line description of the diagram, usable as alt text; null when invalid. | | `outline` | `string[]` | yes | The diagram as plain text in reading order, to check it says what you meant; empty when invalid. | | `outlineTruncated` | `boolean` | yes | True when the summary level cut the outline short. Call again with detail "full" to read all of it. | | `size` | `Size \| null` | yes | Natural size of the laid-out diagram; null when invalid. | | `quality` | `Quality` | no | How clean the picture looks (score, grade, measures) and up to three changes that would help; read it instead of looking. Absent when invalid. | | `quality.score` | `integer` | yes | 0 to 100; 100 is a picture with nothing to fix. The same diagram always gets the same score. minimum 0; maximum 100. | | `quality.grade` | `"clean" \| "fair" \| "cluttered"` | yes | clean is 85 or more, fair 60 or more, cluttered below. | | `quality.measures` | `object` | yes | What the score is made from: crossings, lineOverBox, labelOverlaps, smallestTextPx (in an 880px column), aspectRatio, emptySpace (0 to 1), longestLine (px), detour (1 is straight), the counts of boxes, lines, labels, series, and slices, and any measure the kind adds. | | `quality.hints` | `string[]` | yes | Up to three changes, the one worth most first, each naming the statement or option to edit. | | `quality.line` | `string` | yes | The grade, the score, and the first hint in one line. | | `notices` | `Note[]` | no | Information notes, such as an icon alias drawn as the real icon; absent when there is none. | | `notices[].code` | `string` | yes | A stable code, such as I122 for an icon alias. | | `notices[].severity` | `"info"` | yes | A note never stops a drawing and is not a warning. | | `notices[].message` | `string` | yes | | | `notices[].line` | `integer` | yes | minimum 1. | | `notices[].column` | `integer` | yes | minimum 1. | | `notices[].length` | `integer` | yes | minimum 1. | | `notices[].hint` | `string \| null` | yes | The text to write instead. | | `renders` | `object[]` | yes | One picture per requested color scheme, light first; empty when invalid, when svg is never, and when every picture was left out (see picturesOmitted). | | `renders[].theme` | `"light" \| "dark"` | yes | The color scheme this picture is drawn in. | | `renders[].svg` | `string` | yes | Self-contained SVG markup with an accessible title and description. | | `renders[].size` | `Size` | yes | Natural size of a rendered diagram in px. | | `renders[].size.width` | `integer` | yes | Width in px. minimum 1. | | `renders[].size.height` | `integer` | yes | Height in px. minimum 1. | | `picturesOmitted` | `object` | no | Present only when pictures were left out because the answer would have been too big for an AI client to read in one piece. | | `picturesOmitted.themes` | `("light" \| "dark")[]` | yes | The color schemes whose pictures were left out. | | `picturesOmitted.characters` | `integer` | yes | How many characters the pictures left out came to. | | `picturesOmitted.limit` | `integer` | yes | The most characters one answer may hold before the pictures are left out. | | `picturesOmitted.why` | `string` | yes | Why they were left out, and what to do. | | `viewUrl` | `string` | no | A link that shows a valid diagram in a browser; give it to the person who wants to look. The source is in the part after #, never sent to a server, but anyone with the link can read it. Absent when too long; next says so. | | `pngUrl` | `string` | no | The viewUrl with &download=png: opening it in a browser saves the diagram as a PNG for Slack, email, or a wiki. The PNG is drawn in the person's browser, not on a server, so it is a link to open, not an image address to embed. Absent when too long. | | `svgUrl` | `string` | no | The viewUrl with &download=svg: a person opens it to download the SVG. Not a file address for curl; use saveCommand. Absent when too long. | | `saveCommand` | `string` | no | Run in a shell to save the SVG into the repository: the CLI decodes the link locally, fetches nothing, and embeds the source. Set --out to the path; add --theme dark for dark. Absent when too long. | | `next` | `string` | yes | The next step in one sentence. | ## Behavior hints | Hint | Value | |---|---| | `readOnlyHint` | true | | `destructiveHint` | false | | `idempotentHint` | true | | `openWorldHint` | false | ## See also | Doc | Why | |---|---| | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Every tool, with the workflows first | | [`POST /v1/patterns/draw`](https://docs.plotwork.dev/api/post-v1-patterns-draw.md) | The same operation over HTTP | | [Language reference](https://docs.plotwork.dev/reference/language.md) | What the diagram source may contain | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop in practice | --- # read_diagram > Page: https://docs.plotwork.dev/mcp/tools/read-diagram. Index of every page: https://docs.plotwork.dev/llms.txt Read the source of an existing Plotwork SVG so you can edit the diagram. It is a single tool of the MCP server and the HTTP API, with the title "Read diagram". ## TL;DR | Question | Answer | |---|---| | What is it? | A single tool: it does one thing. | | What does it do? | Read the source of an existing Plotwork SVG so you can edit the diagram. | | How do I call it over HTTP? | [`POST /v1/diagrams/read`](https://docs.plotwork.dev/api/post-v1-diagrams-read.md) | | Does it change anything? | No. It is read-only and idempotent. It saves no file and returns its answer as data. | | Which bounds apply? | None beyond the limits of each argument, which are in the schema below. | | Where do I read its schema as data? | An MCP client gets the name, description, and JSON Schema of every tool from `tools/list`. | ## What it does Read the source of an existing Plotwork SVG so you can edit the diagram. Send the text of an .svg file as svg, or a hosted link (https://cdn.plotwork.dev/d/<22 characters>.svg) as url. Returns the exact source, its kind, the stored diagram id if it was published, and a parse check. Call it first when asked to change a diagram that already exists, then edit the source and call draw_diagram (or edit_diagram); next says how to save or publish it again. Only a picture saved with embedSource true (or by sync_markdown) carries its source; otherwise status is no-source, with the title, outline, and labels to rebuild it from. A text that is not an SVG and a refused or unreachable link come back as data. Only cdn.plotwork.dev and cdn-preview.plotwork.dev are fetched (https, no redirects, 5 seconds, 1 MB); svg reads nothing from the network. ## Arguments | Field | Type | Required | Description | |---|---|---|---| | `svg` | `string` | no | The text of a Plotwork SVG: the content of an .svg file in the repository. Send this or url, not both. at least 1 character; at most 1000000 characters. | | `url` | `string` | no | The link of a hosted Plotwork picture, https://cdn.plotwork.dev/d/<22 characters>.svg. Only Plotwork's own picture hosts are fetched. Send this or svg, not both. at least 1 character; at most 200 characters. | ## Result | Field | Type | Required | Description | |---|---|---|---| | `status` | `"read" \| "no-source" \| "damaged" \| "not-a-picture" \| "refused" \| "unreachable"` | yes | read: the source was found. no-source: the picture carries none. damaged: it carries one that cannot be read back. not-a-picture: the text is not an SVG. refused: the link is not one this tool fetches. unreachable: the link could not be fetched. | | `hasSource` | `boolean` | yes | True when source is set. | | `source` | `string \| null` | yes | The exact source the picture was drawn from, ready to edit and send to draw_diagram; null when there is none. | | `kind` | `string \| null` | yes | The diagram kind of the source, or null. | | `id` | `string \| null` | yes | The id of the stored diagram when the picture was published (data-plotwork-id), or null. | | `from` | `"text" \| "url"` | yes | Where the picture came from: the text that was sent, or the link that was fetched. | | `validation` | `object \| null` | yes | The check of the source, or null when there is none. It parses the source; draw_diagram also lays it out and returns warnings. | | `picture` | `object \| null` | yes | What the picture says about itself, or null when the text is not an SVG. | | `message` | `string` | yes | What happened, in one sentence. | | `next` | `string` | yes | The next step in one sentence. | ## Behavior hints | Hint | Value | |---|---| | `readOnlyHint` | true | | `destructiveHint` | false | | `idempotentHint` | true | | `openWorldHint` | true | ## See also | Doc | Why | |---|---| | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Every tool, with the workflows first | | [`POST /v1/diagrams/read`](https://docs.plotwork.dev/api/post-v1-diagrams-read.md) | The same operation over HTTP | | [Language reference](https://docs.plotwork.dev/reference/language.md) | What the diagram source may contain | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop in practice | --- # compare_diagrams > Page: https://docs.plotwork.dev/mcp/tools/compare-diagrams. Index of every page: https://docs.plotwork.dev/llms.txt Compare two versions of a diagram by meaning, for a pull request review. It is a single tool of the MCP server and the HTTP API, with the title "Compare diagrams". ## TL;DR | Question | Answer | |---|---| | What is it? | A single tool: it does one thing. | | What does it do? | Compare two versions of a diagram by meaning, for a pull request review. | | How do I call it over HTTP? | [`POST /v1/diagrams/compare`](https://docs.plotwork.dev/api/post-v1-diagrams-compare.md) | | Does it change anything? | No. It is read-only and idempotent. It saves no file and returns its answer as data. | | Which bounds apply? | `maxCompareChanges`. See the bounds below. | | Where do I read its schema as data? | An MCP client gets the name, description, and JSON Schema of every tool from `tools/list`. | ## What it does Compare two versions of a diagram by meaning, for a pull request review. Send before and after, each as source, the text of a Plotwork SVG that carries its source, or a cdn.plotwork.dev link. Returns every statement added, removed, renamed (by id or label), moved to another block, or changed (options, type, edge arrow or label), with counts and a plain-language summary; markdown true adds a block ready to paste as a comment. Reordered or reformatted lines are no change. A side with no source is reported, not an error. Lays nothing out; fetches only a link. ## Arguments | Field | Type | Required | Description | |---|---|---|---| | `before` | `string` | yes | The old diagram: source, the text of a Plotwork SVG that carries its source, or a cdn.plotwork.dev link. at least 1 character; at most 1000000 characters. | | `after` | `string` | yes | The new diagram, in any of the same three forms. at least 1 character; at most 1000000 characters. | | `markdown` | `boolean` | no | Also return a Markdown block ready to paste as a pull request comment. default false. | ## Result | Field | Type | Required | Description | |---|---|---|---| | `same` | `boolean` | yes | | | `before` | `object` | yes | | | `before.from` | `"source" \| "svg" \| "url"` | yes | | | `before.read` | `boolean` | yes | False when no source could be read from it; message says why. | | `before.kind` | `string \| null` | yes | | | `before.valid` | `boolean` | yes | | | `before.message` | `string \| null` | yes | | | `after` | `object` | yes | | | `after.from` | `"source" \| "svg" \| "url"` | yes | | | `after.read` | `boolean` | yes | False when no source could be read from it; message says why. | | `after.kind` | `string \| null` | yes | | | `after.valid` | `boolean` | yes | | | `after.message` | `string \| null` | yes | | | `counts` | `object` | yes | | | `counts.added` | `integer` | yes | | | `counts.removed` | `integer` | yes | | | `counts.renamed` | `integer` | yes | | | `counts.moved` | `integer` | yes | | | `counts.changed` | `integer` | yes | | | `changes` | `object[]` | yes | | | `changes[].change` | `"added" \| "removed" \| "renamed" \| "moved" \| "changed"` | yes | | | `changes[].subject` | `"header" \| "node" \| "group" \| "edge" \| "row" \| "series" \| "line"` | yes | | | `changes[].name` | `string` | yes | The id, the label in quotes, or for an edge its ends. | | `changes[].line` | `integer \| null` | yes | The line in after, or in before for a removal. | | `changes[].detail` | `string` | yes | | | `truncated` | `boolean` | yes | True when more changes were found than are listed; counts covers all of them. | | `summary` | `string` | yes | The changes in plain words, for a pull request review. | | `markdown` | `string \| null` | yes | | | `next` | `string` | yes | | ## Behavior hints | Hint | Value | |---|---| | `readOnlyHint` | true | | `destructiveHint` | false | | `idempotentHint` | true | | `openWorldHint` | true | ## Bounds A bound that is reached is reported in the result; nothing is cut silently. | Bound | Value | What it limits | |---|---|---| | `maxCompareChanges` | 200 | Changes listed in one answer; counts and the summary cover every change. | ## See also | Doc | Why | |---|---| | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Every tool, with the workflows first | | [`POST /v1/diagrams/compare`](https://docs.plotwork.dev/api/post-v1-diagrams-compare.md) | The same operation over HTTP | | [Language reference](https://docs.plotwork.dev/reference/language.md) | What the diagram source may contain | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop in practice | --- # get_diagram_syntax > Page: https://docs.plotwork.dev/mcp/tools/get-diagram-syntax. Index of every page: https://docs.plotwork.dev/llms.txt Read the diagram language as data. It is a single tool of the MCP server and the HTTP API, with the title "Get diagram syntax". ## TL;DR | Question | Answer | |---|---| | What is it? | A single tool: it does one thing. | | What does it do? | Read the diagram language as data. | | How do I call it over HTTP? | [`GET /v1/vocabulary`](https://docs.plotwork.dev/api/get-v1-vocabulary.md) | | Does it change anything? | No. It is read-only and idempotent, and it talks to nothing outside itself. It saves no file and returns its answer as data. | | Which bounds apply? | None beyond the limits of each argument, which are in the schema below. | | Where do I read its schema as data? | An MCP client gets the name, description, and JSON Schema of every tool from `tools/list`. | ## What it does Read the diagram language as data. Call it once with no kind before your first diagram: the overview, the tables every kind shares (node types, arrows including dotted ..> for asynchronous calls, animated and numbered lines, icon sets, size limits, how to edit an existing SVG), and one line about each kind. For one kind in full, call it again with kind set to it: its statements, limits, and a tested example. kind flow adds the group types (type=), overlays, stack= marks, and a complete cloud architecture example; build the group tree top-down with type=, one node per service, and icons from map_icons or search_icons. For a common architecture, find_pattern gives a checked diagram to adapt. topic import-formats lists what import_source reads. ## Arguments | Field | Type | Required | Description | |---|---|---|---| | `kind` | `"flow" \| "sequence" \| "schema" \| "git" \| "gantt" \| "class" \| "state" \| "timeline" \| "journey" \| "mindmap" \| "treemap" \| "pie" \| "quadrant" \| "layers" \| "c4" \| "matrix" \| "sankey" \| "xychart" \| "tree" \| "swimlane"` | no | The one kind to read in full. Omit it for the overview: the language every kind shares and one line about each kind. | | `topic` | `"import-formats"` | no | import-formats: what import_source reads, draws, and takes, a line each. | ## Result | Field | Type | Required | Description | |---|---|---|---| | `answer` | `string` | yes | What this answer is: overview, a topic (import-formats), or the kind it describes in full (flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today; later versions add kinds, so expect others). | | `howToGetAKind` | `string` | yes | In plain words: what this answer holds and how to get the rest. | | `kinds` | `string[]` | yes | Header keywords: the first line names the kind. | | `kindDetails` | `object[]` | yes | The overview lists every kind in the order of kinds, with its header, noun, and summary, and a pointer in more. A kind answer holds that kind alone, in full. | | `kindDetails[].kind` | `string` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `kindDetails[].header` | `string` | yes | The header line, such as flow "Title". | | `kindDetails[].noun` | `string` | yes | What the kind's diagrams are called in a sentence, such as flow or database schema. | | `kindDetails[].summary` | `string` | yes | One sentence: what the kind draws. | | `kindDetails[].statements` | `object[]` | no | The statements the kind adds to the header. Empty for a kind whose language the shared tables of this reference already describe. | | `kindDetails[].statements[].syntax` | `string` | yes | The statement with its parts named, such as: task <id> "Name" days=<n>. | | `kindDetails[].statements[].meaning` | `string` | yes | What the statement does, and what it needs. | | `kindDetails[].statements[].example` | `string` | yes | One line that parses. | | `kindDetails[].limits` | `object[]` | no | The quality thresholds the kind adds. Empty for a kind whose limits are in the limits table of this reference. | | `kindDetails[].limits[].name` | `string` | yes | What is counted, such as tasks. | | `kindDetails[].limits[].value` | `integer` | yes | More than this in one diagram draws a quality warning. | | `kindDetails[].limits[].meaning` | `string` | yes | Why the limit exists and what to do past it. | | `kindDetails[].more` | `string` | no | Only in the overview, where statements and limits are left out: what a call with this kind returns that the overview does not, and how to ask for it. | | `nodeTypes` | `object[]` | no | | | `nodeTypes[].type` | `"person" \| "agent" \| "client" \| "service" \| "gateway" \| "function" \| "database" \| "cache" \| "queue" \| "storage" \| "identity" \| "secret" \| "external" \| "step" \| "decision" \| "terminal"` | yes | | | `nodeTypes[].shape` | `"card" \| "pill"` | yes | How the node is drawn. | | `nodeTypes[].isTinted` | `boolean` | yes | True when the card is filled and outlined in its tone, as for decisions. | | `nodeTypes[].defaultTone` | `"neutral" \| "blue" \| "violet" \| "aqua" \| "orange" \| "green" \| "amber" \| "red"` | yes | Color family used when tone= is not set. | | `nodeTypes[].defaultIcon` | `string \| null` | yes | Icon used when icon= is not set, or null for none. | | `nodeTypes[].isDashed` | `boolean` | yes | True when the outline is dashed, as for external systems. | | `nodeTypes[].usableAsActor` | `boolean` | yes | True when a sequence diagram actor may use this type. | | `tones` | `("neutral" \| "blue" \| "violet" \| "aqua" \| "orange" \| "green" \| "amber" \| "red")[]` | no | | | `directions` | `("auto" \| "right" \| "down" \| "left" \| "up")[]` | no | Values for direction= on a flow header. | | `arrows` | `object[]` | no | | | `arrows[].token` | `string` | yes | The arrow as written in source. | | `arrows[].line` | `"solid" \| "dashed" \| "dotted"` | yes | How the line is drawn. | | `arrows[].heads` | `"end" \| "both" \| "none"` | yes | Which ends carry an arrowhead. | | `arrows[].meaning` | `string` | yes | What the arrow says, so the right one is chosen. | | `blockTypes` | `("alt" \| "opt" \| "loop" \| "par")[]` | no | Frames a sequence diagram can wrap around messages. | | `columnFlags` | `("pk" \| "fk" \| "unique" \| "nullable")[]` | no | Flags a schema column row may carry. | | `themes` | `("light" \| "dark")[]` | no | | | `iconSets` | `("lucide" \| "simple" \| "status" \| "aws" \| "gcp" \| "azure" \| "alibaba" \| "oci" \| "ibm" \| "cloudflare" \| "digitalocean" \| "vercel")[]` | no | | | `iconCount` | `integer` | no | Number of bundled icons; use search_icons to find names. | | `iconProviders` | `object[]` | no | Cloud and platform providers, each an icon set holding the provider (set:cloud) and its products. | | `iconProviders[].set` | `"aws" \| "gcp" \| "azure" \| "alibaba" \| "oci" \| "ibm" \| "cloudflare" \| "digitalocean" \| "vercel"` | yes | Icon set prefix, as in aws:lambda. | | `iconProviders[].title` | `string` | yes | The provider the set stands for. | | `iconProviders[].iconCount` | `integer` | yes | Icons in the set: the provider itself (set:cloud) and its products. | | `iconProviders[].example` | `string` | yes | One icon of the set that is bundled, for example aws:lambda. | | `overlay` | `object` | no | A dashed frame around members that may sit in different groups; it owns none of them. | | `overlay.options` | `string[]` | yes | Options an overlay statement takes after its name, as in: overlay id "Name" members=a,b type=scale-group. | | `overlay.frame` | `"solid" \| "dashed" \| "dotted"` | yes | How an overlay is always drawn. | | `overlay.meaning` | `string` | yes | What an overlay is for, so it is used where a group would be wrong. | | `group` | `object` | no | How a flow group looks and what it accepts; its corner tab shows its icon and name. | | `group.options` | `string[]` | yes | Options a group statement takes after its name, as in: group id "Name" type=region provider=aws {. | | `group.defaultTone` | `"neutral" \| "blue" \| "violet" \| "aqua" \| "orange" \| "green" \| "amber" \| "red"` | yes | Color family of a group with no type when tone= is not set. | | `group.example` | `object` | yes | A complete architecture diagram: typed groups nested by provider, a provider icon on every node, stack marks, and a dotted edge. Copy and edit it. | | `group.example.kind` | `string` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `group.example.title` | `string` | yes | | | `group.example.source` | `string` | yes | A complete diagram that is checked by the build; copy and edit it. | | `group.types` | `object[]` | yes | Every group type. A type gives the group a default icon, tone, frame style, and a shade that deepens with nesting; icon= and tone= override them. | | `group.types[].type` | `"cloud" \| "account" \| "region" \| "zone" \| "network" \| "subnet" \| "public-subnet" \| "private-subnet" \| "management-subnet" \| "isolated-subnet" \| "resource-group" \| "cluster" \| "scale-group" \| "service-plan" \| "web-tier" \| "app-tier" \| "data-tier" \| "storage-account" \| "messaging" \| "edge" \| "security" \| "identity-boundary" \| "trust-boundary" \| "observability" \| "ci-cd" \| "environment" \| "cloud-boundary" \| "on-premises" \| "client-side" \| "external-systems"` | yes | Value for type= on a group. | | `group.types[].title` | `string` | yes | What the frame is called in the text description. | | `group.types[].meaning` | `string` | yes | What the author means by this type, so the right one is chosen. | | `group.types[].tone` | `"neutral" \| "blue" \| "violet" \| "aqua" \| "orange" \| "green" \| "amber" \| "red"` | yes | Color family the frame and tab get when tone= is not set. | | `group.types[].frame` | `"solid" \| "dashed" \| "dotted"` | yes | How the outline is drawn. | | `group.types[].icon` | `string` | yes | Icon in the corner tab when the group has no provider. | | `group.types[].providerIcons` | `object` | yes | Icon in the corner tab for each provider whose icon differs from the generic one, keyed by provider set. | | `lineOptions` | `object[]` | no | Options that animate a line (animate=on makes dashes travel toward the arrowhead) or number the steps of a flow or sequence (numbered=on, start=, and step= on a line). All are off by default. | | `lineOptions[].key` | `string` | yes | The option as written, such as animate or step. | | `lineOptions[].scope` | `"header" \| "line"` | yes | header: on the flow or sequence header line. line: on an edge or a message. | | `lineOptions[].values` | `string` | yes | What the option takes. | | `lineOptions[].meaning` | `string` | yes | What it does, and the cases where it is refused. | | `lineOptions[].example` | `string` | yes | One line that parses. | | `labelIcons` | `object[]` | no | Edge labels that get a tick or cross automatically; icon=none on the edge removes it. | | `labelIcons[].label` | `string` | yes | An edge label that gets an icon automatically, matched whole and ignoring case. | | `labelIcons[].icon` | `string` | yes | The icon the label gets. | | `verbIcons` | `object[]` | no | The icons a flow edge label draws when it names a relationship with a verb. icon= on the edge wins, icon=none removes it, and icons=off on the header turns them all off. | | `verbIcons[].icon` | `string` | yes | The icon drawn in the label. | | `verbIcons[].words` | `string[]` | yes | Phrases that draw it: the whole label, or its first words followed by what is acted on ("reads orders"). | | `style` | `string[]` | no | How to make a flow diagram look finished: verbs on arrows, role= on lines that mean something, typed groups, product names. | | `autoIcons` | `object` | no | Items with no icon= get the icon their label names for certain (a brand, a cloud product, a common role). icon=none keeps one item plain; icons=off on the header keeps them all plain. | | `autoIcons.key` | `string` | yes | The header option: icons. | | `autoIcons.values` | `string` | yes | What it takes. | | `autoIcons.meaning` | `string` | yes | What it does, and how one item opts out. | | `autoIcons.example` | `string` | yes | One header line that parses. | | `roleIcons` | `object[]` | no | The plain icons for common roles, drawn on flow and sequence service and external items, C4 elements, layers blocks, mind map topics, and swimlane lanes. The last role word of the label wins. | | `roleIcons[].icon` | `string` | yes | The icon drawn. | | `roleIcons[].words` | `string[]` | yes | Whole words of a label of three words or fewer that draw it. | | `statusIcons` | `object[]` | no | Colored status and outcome icons: a green check, a red X, and so on. | | `statusIcons[].ref` | `string` | yes | Value for icon=, such as status:success. | | `statusIcons[].tone` | `"neutral" \| "blue" \| "violet" \| "aqua" \| "orange" \| "green" \| "amber" \| "red"` | yes | Color the icon is always drawn in, whatever tone its owner has. | | `statusIcons[].meaning` | `string` | yes | What the icon says, so the right one is chosen. | | `limits` | `object` | no | Bounds and quality thresholds. | | `limits.maxSourceLength` | `integer` | yes | | | `limits.maxFlowNodes` | `integer` | yes | Quality lint: more nodes than this warns, for a diagram with no groups. | | `limits.nodesPerGroup` | `integer` | yes | Each group of a flow diagram raises the node limit by this many. | | `limits.maxFlowNodesCeiling` | `integer` | yes | The node limit never rises above this, whatever the groups. | | `limits.maxSchemaTables` | `integer` | yes | | | `limits.maxSequenceActors` | `integer` | yes | | | `limits.maxNumberedSteps` | `integer` | yes | More numbered steps than this in one diagram warns (W200). | | `limits.maxStackMarks` | `integer` | yes | Most technology marks a node carries in stack=. | | `limits.targetWidth` | `integer` | yes | Column width in px that diagrams are checked against. | | `editing` | `object` | no | How to edit an existing Plotwork SVG: read it back with read_diagram, change the source, draw it, and save or publish it again. | | `editing.steps` | `string[]` | yes | In order: how to change a diagram that already exists as a Plotwork SVG, a hosted link or a file in the repository. | | `editing.embedSource` | `string` | yes | When to set embedSource true on draw_diagram, render_diagram, or publish_diagram, and what it means for who can read the source. | | `diagnosticCodes` | `object[]` | no | | | `diagnosticCodes[].name` | `string` | yes | Identifier of the code. | | `diagnosticCodes[].code` | `string` | yes | The code to branch on, such as E120. | | `diagnosticCodes[].severity` | `"error" \| "warning"` | yes | | | `examples` | `object[]` | yes | Tested examples: the overview holds the ones for the kinds the shared tables describe in full, a kind answer holds its own. | | `examples[].kind` | `string` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `examples[].title` | `string` | yes | | | `examples[].source` | `string` | yes | A complete diagram that is checked by the build; copy and edit it. | | `importFormats` | `object[]` | no | Only for topic import-formats: every format import_source reads, a line each. | | `importFormats[].format` | `string` | yes | The format, as import_source takes it. | | `importFormats[].reads` | `string` | yes | What it reads. | | `importFormats[].draws` | `string` | yes | The diagram it draws. | | `importFormats[].options` | `string` | yes | The options it takes. | ## Behavior hints | Hint | Value | |---|---| | `readOnlyHint` | true | | `destructiveHint` | false | | `idempotentHint` | true | | `openWorldHint` | false | ## See also | Doc | Why | |---|---| | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Every tool, with the workflows first | | [`GET /v1/vocabulary`](https://docs.plotwork.dev/api/get-v1-vocabulary.md) | The same operation over HTTP | | [Language reference](https://docs.plotwork.dev/reference/language.md) | What the diagram source may contain | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop in practice | --- # validate_diagram > Page: https://docs.plotwork.dev/mcp/tools/validate-diagram. Index of every page: https://docs.plotwork.dev/llms.txt Check diagram source without drawing it: every error in one pass, each with line, column, and a fix hint, plus quality warnings. It is a single tool of the MCP server and the HTTP API, with the title "Validate diagram". ## TL;DR | Question | Answer | |---|---| | What is it? | A single tool: it does one thing. | | What does it do? | Check diagram source without drawing it: every error in one pass, each with line, column, and a fix hint, plus quality warnings. | | How do I call it over HTTP? | [`POST /v1/diagrams/validate`](https://docs.plotwork.dev/api/post-v1-diagrams-validate.md) | | Does it change anything? | No. It is read-only and idempotent, and it talks to nothing outside itself. It saves no file and returns its answer as data. | | Which bounds apply? | None beyond the limits of each argument, which are in the schema below. | | Where do I read its schema as data? | An MCP client gets the name, description, and JSON Schema of every tool from `tools/list`. | ## What it does Check diagram source without drawing it: every error in one pass, each with line, column, and a fix hint, plus quality warnings. An icon a label names for certain is drawn already; W204 gives the exact icon=provider:name for a node whose label names a product it does not draw (a brand whose name is also an everyday word, or every match under icons=off), W205 the type= for a group whose name matches a group type, and W206 flags an overlay framing a node that is not its member. These suggestions are not applied for you: add one, or icon=none to keep a node plain, and validate again. Prefer draw_diagram, which checks, describes, and draws in one call. ## Arguments | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | Diagram source; the first line is the header, such as flow "Title" (draw_diagram lists every kind). at least 1 character; at most 50000 characters. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## Result | Field | Type | Required | Description | |---|---|---|---| | `valid` | `boolean` | yes | True when there are no errors; warnings do not make a diagram invalid. | | `kind` | `string \| null` | yes | The diagram kind, or null when the header could not be read. | | `errors` | `Diagnostic[]` | yes | Every error found in one pass, so they can be fixed together. | | `errors[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `errors[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `errors[].message` | `string` | yes | What is wrong, in one sentence. | | `errors[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `errors[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `errors[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `errors[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `warnings` | `Diagnostic[]` | yes | Quality warnings for a valid diagram, such as too dense or too wide. | | `warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `summary` | `string \| null` | yes | One-line description of the diagram, or null when invalid. | | `size` | `Size \| null` | yes | Natural rendered size, or null when invalid. | | `quality` | `Quality` | no | How clean the laid-out diagram looks, with up to three changes; absent when invalid. | | `quality.score` | `integer` | yes | 0 to 100; 100 is a picture with nothing to fix. The same diagram always gets the same score. minimum 0; maximum 100. | | `quality.grade` | `"clean" \| "fair" \| "cluttered"` | yes | clean is 85 or more, fair 60 or more, cluttered below. | | `quality.measures` | `object` | yes | What the score is made from: crossings, lineOverBox, labelOverlaps, smallestTextPx (in an 880px column), aspectRatio, emptySpace (0 to 1), longestLine (px), detour (1 is straight), the counts of boxes, lines, labels, series, and slices, and any measure the kind adds. | | `quality.hints` | `string[]` | yes | Up to three changes, the one worth most first, each naming the statement or option to edit. | | `quality.line` | `string` | yes | The grade, the score, and the first hint in one line. | | `notices` | `Note[]` | no | Information notes, such as an icon alias resolved to a real icon; absent when there is none. | | `notices[].code` | `string` | yes | A stable code, such as I122 for an icon alias. | | `notices[].severity` | `"info"` | yes | A note never stops a drawing and is not a warning. | | `notices[].message` | `string` | yes | | | `notices[].line` | `integer` | yes | minimum 1. | | `notices[].column` | `integer` | yes | minimum 1. | | `notices[].length` | `integer` | yes | minimum 1. | | `notices[].hint` | `string \| null` | yes | The text to write instead. | ## Behavior hints | Hint | Value | |---|---| | `readOnlyHint` | true | | `destructiveHint` | false | | `idempotentHint` | true | | `openWorldHint` | false | ## See also | Doc | Why | |---|---| | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Every tool, with the workflows first | | [`POST /v1/diagrams/validate`](https://docs.plotwork.dev/api/post-v1-diagrams-validate.md) | The same operation over HTTP | | [Language reference](https://docs.plotwork.dev/reference/language.md) | What the diagram source may contain | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop in practice | --- # render_diagram > Page: https://docs.plotwork.dev/mcp/tools/render-diagram. Index of every page: https://docs.plotwork.dev/llms.txt Render valid source to a self-contained, accessible SVG in the light or dark theme, with its pixel size and quality warnings. It is a single tool of the MCP server and the HTTP API, with the title "Render diagram". ## TL;DR | Question | Answer | |---|---| | What is it? | A single tool: it does one thing. | | What does it do? | Render valid source to a self-contained, accessible SVG in the light or dark theme, with its pixel size and quality warnings. | | How do I call it over HTTP? | [`POST /v1/diagrams/render`](https://docs.plotwork.dev/api/post-v1-diagrams-render.md) | | Does it change anything? | No. It is read-only and idempotent, and it talks to nothing outside itself. It saves no file and returns its answer as data. | | Which bounds apply? | None beyond the limits of each argument, which are in the schema below. | | Where do I read its schema as data? | An MCP client gets the name, description, and JSON Schema of every tool from `tools/list`. | ## What it does Render valid source to a self-contained, accessible SVG in the light or dark theme, with its pixel size and quality warnings. Source with errors fails with its diagnostics. Set embedSource true to save the SVG in a repository, so read_diagram can give the source back. Prefer draw_diagram, which validates, outlines, and renders in one call. ## Arguments | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | Diagram source; the first line is the header, such as flow "Title" (draw_diagram lists every kind). at least 1 character; at most 50000 characters. | | `theme` | `"light" \| "dark" \| "auto"` | no | Color scheme to paint with: light, dark, or auto for one picture that follows the reader's light or dark setting. default "light". | | `idPrefix` | `string` | no | Prefix for element ids; set it when inlining several diagrams into one page. at most 32 characters; pattern `^[A-Za-z][A-Za-z0-9_-]*$`. | | `embedSource` | `boolean` | no | Write the source into the SVG metadata so read_diagram can give it back. Set it true when you save the SVG into a repository; anyone with the file can then read the source. Default false. default false. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## Result | Field | Type | Required | Description | |---|---|---|---| | `svg` | `string` | yes | Self-contained SVG markup with an accessible title and description. | | `theme` | `"light" \| "dark" \| "auto"` | yes | The color scheme used. | | `size` | `Size` | yes | Natural size of a rendered diagram in px. | | `size.width` | `integer` | yes | Width in px. minimum 1. | | `size.height` | `integer` | yes | Height in px. minimum 1. | | `warnings` | `Diagnostic[]` | yes | | | `warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | ## Behavior hints | Hint | Value | |---|---| | `readOnlyHint` | true | | `destructiveHint` | false | | `idempotentHint` | true | | `openWorldHint` | false | ## See also | Doc | Why | |---|---| | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Every tool, with the workflows first | | [`POST /v1/diagrams/render`](https://docs.plotwork.dev/api/post-v1-diagrams-render.md) | The same operation over HTTP | | [Language reference](https://docs.plotwork.dev/reference/language.md) | What the diagram source may contain | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop in practice | --- # outline_diagram > Page: https://docs.plotwork.dev/mcp/tools/outline-diagram. Index of every page: https://docs.plotwork.dev/llms.txt Describe valid diagram source as plain text: participants, steps, tables, and connections in reading order, to check the diagram says what you meant or to use as alt text. It is a single tool of the MCP server and the HTTP API, with the title "Outline diagram". ## TL;DR | Question | Answer | |---|---| | What is it? | A single tool: it does one thing. | | What does it do? | Describe valid diagram source as plain text: participants, steps, tables, and connections in reading order, to check the diagram says what you meant or to use as alt text. | | How do I call it over HTTP? | [`POST /v1/diagrams/outline`](https://docs.plotwork.dev/api/post-v1-diagrams-outline.md) | | Does it change anything? | No. It is read-only and idempotent, and it talks to nothing outside itself. It saves no file and returns its answer as data. | | Which bounds apply? | None beyond the limits of each argument, which are in the schema below. | | Where do I read its schema as data? | An MCP client gets the name, description, and JSON Schema of every tool from `tools/list`. | ## What it does Describe valid diagram source as plain text: participants, steps, tables, and connections in reading order, to check the diagram says what you meant or to use as alt text. Source with errors fails with diagnostics. Does not render. Prefer draw_diagram, which returns the outline with the check and the pictures. ## Arguments | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | Diagram source; the first line is the header, such as flow "Title" (draw_diagram lists every kind). at least 1 character; at most 50000 characters. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## Result | Field | Type | Required | Description | |---|---|---|---| | `kind` | `string` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `summary` | `string` | yes | One-line description. | | `outline` | `string[]` | yes | Lines in reading order: participants, steps, tables, and connections. | ## Behavior hints | Hint | Value | |---|---| | `readOnlyHint` | true | | `destructiveHint` | false | | `idempotentHint` | true | | `openWorldHint` | false | ## See also | Doc | Why | |---|---| | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Every tool, with the workflows first | | [`POST /v1/diagrams/outline`](https://docs.plotwork.dev/api/post-v1-diagrams-outline.md) | The same operation over HTTP | | [Language reference](https://docs.plotwork.dev/reference/language.md) | What the diagram source may contain | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop in practice | --- # search_icons > Page: https://docs.plotwork.dev/mcp/tools/search-icons. Index of every page: https://docs.plotwork.dev/llms.txt Find bundled icon names by keyword, idea, product, or abbreviation ("postgres", "token exchange", "s3", "cosmos db"). It is a single tool of the MCP server and the HTTP API, with the title "Search icons". ## TL;DR | Question | Answer | |---|---| | What is it? | A single tool: it does one thing. | | What does it do? | Find bundled icon names by keyword, idea, product, or abbreviation ("postgres", "token exchange", "s3", "cosmos db"). | | How do I call it over HTTP? | [`GET /v1/icons`](https://docs.plotwork.dev/api/get-v1-icons.md) | | Does it change anything? | No. It is read-only and idempotent, and it talks to nothing outside itself. It saves no file and returns its answer as data. | | Which bounds apply? | None beyond the limits of each argument, which are in the schema below. | | Where do I read its schema as data? | An MCP client gets the name, description, and JSON Schema of every tool from `tools/list`. | ## What it does Find bundled icon names by keyword, idea, product, or abbreviation ("postgres", "token exchange", "s3", "cosmos db"). Returns set:name references, best first, at most 100, such as lucide:database, simple:postgresql, aws:lambda. Cloud products are in one set per provider (aws, gcp, azure, alibaba, oci, ibm, cloudflare, digitalocean, vercel), with every official aws, azure, and gcp icon; <provider>:cloud is the provider, for a group. Use it before setting icon= or a name in stack=. Search with the product name exactly as the user wrote it and take the first result; never invent a name, because an unknown icon is an error. For several names prefer map_icons: up to 40 in one call, and it never guesses. Hosted calls also find the organisation icons, as org:name. ## Arguments | Field | Type | Required | Description | |---|---|---|---| | `q` | `string` | yes | Words to look for: a name such as "database", an idea such as "token exchange", or a cloud product such as "lambda" or "gke". at least 1 character; at most 64 characters. | | `limit` | `integer` | no | Most names to return. minimum 1; maximum 100; default 20. | ## Result | Field | Type | Required | Description | |---|---|---|---| | `query` | `string` | yes | The query that was searched. | | `items` | `string[]` | yes | Matching icon references as set:name, best match first. | ## Behavior hints | Hint | Value | |---|---| | `readOnlyHint` | true | | `destructiveHint` | false | | `idempotentHint` | true | | `openWorldHint` | false | ## See also | Doc | Why | |---|---| | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | Every tool, with the workflows first | | [`GET /v1/icons`](https://docs.plotwork.dev/api/get-v1-icons.md) | The same operation over HTTP | | [Language reference](https://docs.plotwork.dev/reference/language.md) | What the diagram source may contain | | [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The loop in practice | --- # The Plotwork command line > Page: https://docs.plotwork.dev/cli/overview. Index of every page: https://docs.plotwork.dev/llms.txt `plotwork` checks, draws, and syncs Plotwork diagrams in a repository, on your machine and in CI. It is the npm package `@buun_group/plotwork`, built from the same engine as the MCP server, the HTTP API, and the GitHub Action, so a picture it draws is the picture they draw. ## TL;DR | Question | Answer | |---|---| | What does it do? | Draws the pictures under every `diagram` fence of your Markdown files, fails CI when one is stale, checks diagrams and style rules, makes diagrams from Terraform, OpenAPI, SQL and other files, converts Mermaid, and reads the source back out of a picture. | | How do I run it? | `npx -y @buun_group/plotwork@0.1.0-beta.13 <command>`. Nothing to install beyond Node. | | Which Node version? | Node 22 or newer. | | Does it need an account or a key? | No. Only `check --org`, which reads an organisation's saved rules, needs a key. | | Does it use the network? | Not by default. It reads and writes the files you name. `read` fetches a hosted link you give it, and `check --org` reads an organisation's rules; nothing else makes a request. | | Does it count anything? | No. It keeps no usage counts and sends none. | | Where is every command? | [Command reference](https://docs.plotwork.dev/cli/commands.md), one page for each command, and `plotwork <command> --help` in a terminal. | | How is it different from the GitHub Action? | The Action runs the same engine inside GitHub Actions with inputs instead of flags. The command line runs anywhere Node runs: your machine, GitLab, Azure Pipelines, Jenkins, CircleCI. | ## Run it with npx or install it There are two ways to run it. Both run the same release. | Way | Command | Use it when | |---|---|---| | npx, pinned | `npx -y @buun_group/plotwork@0.1.0-beta.13 sync --check "docs/**/*.md"` | In CI, and for a one-off run. Nothing is added to the project. | | A dev dependency | `npm install --save-dev --save-exact @buun_group/plotwork@0.1.0-beta.13`, then `npx plotwork sync` | The project already has Node tooling, and you want the version in its lock file. | | Installed for your user | `npm install --global @buun_group/plotwork@0.1.0-beta.13`, then `plotwork sync` | You run it often across many repositories. | Pin the exact version everywhere. A new release can draw a picture a little differently, and a check that runs the latest release would then fail on a pull request that changed nothing. Move the pin in a change you review, and run `sync` in the same change. `plotwork --version` prints the release, and `plotwork --help` lists every command. ## What it does | Job | Command | |---|---| | Draw the pictures under every diagram fence of a page, and keep them in step | [`plotwork sync`](https://docs.plotwork.dev/cli/commands/sync.md) | | Fail CI when a picture is stale | [`plotwork sync --check`](https://docs.plotwork.dev/cli/commands/sync.md) | | Check a diagram for mistakes, with a line, a column, and a hint | [`plotwork validate`](https://docs.plotwork.dev/cli/commands/validate.md) | | Fail CI when a diagram breaks your team's style rules | [`plotwork check`](https://docs.plotwork.dev/cli/commands/check.md) | | Draw one diagram to an SVG | [`plotwork render`](https://docs.plotwork.dev/cli/commands/render.md) | | Get the source back from a picture | [`plotwork read`](https://docs.plotwork.dev/cli/commands/read.md) | | Make diagrams from Terraform, OpenAPI, Compose and other files, and fail CI on drift | [`plotwork generate`](https://docs.plotwork.dev/cli/commands/generate.md) | | Draw one file of another format once | [`plotwork import-source`](https://docs.plotwork.dev/cli/commands/import-source.md) | | Convert Mermaid | [`plotwork import`](https://docs.plotwork.dev/cli/commands/import.md) | | Paste a diagram into Confluence or Notion | [`plotwork export`](https://docs.plotwork.dev/cli/commands/export.md) | ## Where it runs On a developer's machine, `plotwork sync` redraws the pictures before a commit. In CI, `plotwork sync --check` and `plotwork generate --check` fail the build when a picture or a generated diagram is out of step. On GitHub the [GitHub Action](https://docs.plotwork.dev/diagrams-in-ci.md) does the same with annotations on the pull request, and on GitLab and every other CI the command line does it with an exit code. [CI usage](https://docs.plotwork.dev/cli/ci.md) has a pipeline for each. ## Privacy The command line runs on your machine and keeps nothing. It counts no use and sends no totals: `PLOTWORK_USAGE` and `DO_NOT_TRACK`, which control the local MCP server's opt-in counts, have no effect on it. The two requests it can make are the ones you ask for: `read` with a hosted link, and `check --org`. [Environment variables](https://docs.plotwork.dev/cli/environment.md) lists everything it reads. ## See also | Doc | Why | |---|---| | [Quick start](https://docs.plotwork.dev/cli/quick-start.md) | A first diagram, from a fence to a checked pull request | | [Command reference](https://docs.plotwork.dev/cli/commands.md) | Every command, option, and default | | [CI usage](https://docs.plotwork.dev/cli/ci.md) | GitHub Actions, GitLab CI, Azure Pipelines, Jenkins, and CircleCI | | [Configuration](https://docs.plotwork.dev/cli/configuration.md) | The repository settings every command reads | | [MCP server](https://docs.plotwork.dev/mcp/overview.md) | The same engine for an AI agent | --- # Command line quick start > Page: https://docs.plotwork.dev/cli/quick-start. Index of every page: https://docs.plotwork.dev/llms.txt From an empty page to a diagram that CI keeps in step, in five steps. You need Node 22 or newer and a repository with a Markdown file. ## TL;DR | Question | Answer | |---|---| | What do I install? | Nothing. `npx` fetches the pinned release the first time and keeps it in its cache. | | What do I write? | A `diagram` fence in a Markdown file. | | What draws it? | `npx -y @buun_group/plotwork@0.1.0-beta.13 sync README.md`, which writes a light and a dark SVG and the picture block under the fence. | | What do I commit? | The Markdown file and the SVGs it wrote. | | What fails CI? | `sync --check`, when the source changed and the picture did not. | ## 1. Write a diagram Add a fence to `README.md`: ````md ```diagram flow "Checkout" direction=down node shop service "Shop" node pay service "Payments" node db database "Orders DB" shop -> pay : "charge" pay -> db : "record" ``` ```` The fence is the source. The language is in [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md), and the [Language reference](https://docs.plotwork.dev/reference/language.md) has every statement. ## 2. Check it ```sh npx -y @buun_group/plotwork@0.1.0-beta.13 validate README.md ``` A mistake prints its line and column in the file, a code, and a hint that says how to fix it. The command exits 0 when every diagram is valid and 1 when one is not. ## 3. Draw it ```sh npx -y @buun_group/plotwork@0.1.0-beta.13 sync README.md ``` `sync` writes `assets/diagrams/checkout.svg` and `checkout.dark.svg` beside the page, named after the diagram's title. It folds the fence into a "Diagram source" block and writes a picture block under it that shows the light or the dark picture to match the reader's theme, with a text description for a screen reader. GitHub, GitLab, and every Markdown viewer show the picture, and the fence stays in the page as the source. [Markdown and GitHub](https://docs.plotwork.dev/markdown-and-github.md) shows what the block looks like. Run `sync` again after any change to the source. A fence whose picture is already current is left alone. ## 4. Commit both ```sh git add README.md assets/diagrams git commit -m "docs: add the checkout flow" ``` ## 5. Check it in CI Add one step to your pipeline: ```sh npx -y @buun_group/plotwork@0.1.0-beta.13 sync --check README.md "docs/**/*.md" ``` It writes nothing and exits 1 when a picture is missing or older than its source, or a diagram has a mistake, so a pull request that changes a diagram without redrawing it fails. Quote a glob so the command line expands it the same way in every shell. [CI usage](https://docs.plotwork.dev/cli/ci.md) has the step for GitHub Actions, GitLab CI, Azure Pipelines, Jenkins, and CircleCI. ## Next | To | Run | |---|---| | Set defaults, such as the direction of every flow, for the whole repository | Add `plotwork.config.json`. See [Configuration](https://docs.plotwork.dev/cli/configuration.md). | | Draw your Terraform, OpenAPI, or database schema and keep it in step | `plotwork generate`. See [Diagrams as code](https://docs.plotwork.dev/diagrams-as-code.md). | | Move Mermaid diagrams to Plotwork | `plotwork import README.md --write`. See [Migrating from Mermaid](https://docs.plotwork.dev/migrating-from-mermaid.md). | | Change a picture you already have | `plotwork read picture.svg > picture.diagram`. See [Editing an existing diagram](https://docs.plotwork.dev/editing-existing-diagrams.md). | ## See also | Doc | Why | |---|---| | [Overview](https://docs.plotwork.dev/cli/overview.md) | npx or install, and what each command is for | | [Command reference](https://docs.plotwork.dev/cli/commands.md) | Every command, option, and default | | [Troubleshooting](https://docs.plotwork.dev/cli/troubleshooting.md) | When a step does not do what this page says | --- # Command line configuration > Page: https://docs.plotwork.dev/cli/configuration. Index of every page: https://docs.plotwork.dev/llms.txt How the command line finds your repository settings, which commands use which part of them, and how an option on the command line and a setting combine. The fields of the file are in [Repository settings](https://docs.plotwork.dev/repository-settings.md); this page is about how `plotwork` reads it. ## TL;DR | Question | Answer | |---|---| | Where does the file go? | `plotwork.config.json` at the repository root, or `.plotwork/config.json`. | | How is it found? | `--config <file>`, else the file `PLOTWORK_CONFIG` names, else the nearest `plotwork.config.json` or `.plotwork/config.json` from the diagram's folder up to the folder that holds `.git`. | | Is a file required? | No. Without one, every command draws with the built-in defaults. Only `check` needs `rules`, and `generate` needs `sources`. | | Does an option on the command line win? | Yes. A flag wins over the settings, and the settings win over the built-in defaults. A diagram's own header wins over the settings' defaults. | | What else is configured? | Only environment variables, listed in [Environment variables](https://docs.plotwork.dev/cli/environment.md). There is no other file and no per-user setting. | ## 1. A minimal file ```json { "$schema": "https://docs.plotwork.dev/schemas/plotwork.config.json", "defaults": { "direction": "down", "icons": "auto" }, "rules": { "requireTitle": true, "maxNodes": 40 } } ``` With `$schema`, an editor completes the fields and underlines a mistake. The file needs no account and is never sent anywhere. ## 2. Which commands read what | Section | Read by | What it does there | |---|---|---| | `defaults` | `sync`, `validate`, `check`, `render`, `outline`, `generate` | Fills each header option a diagram leaves out, before the diagram is checked or drawn. | | `rules` | `check` | The style rules the diagrams are checked against. `--org` replaces them with an organisation's saved rules; the defaults still apply. | | `sources` | `generate` | The diagrams made from files of the repository. See [Diagrams as code](https://docs.plotwork.dev/diagrams-as-code.md). | | `brand` | None | Read by hosted calls only. The command line draws in the built-in themes. | `read`, `import`, `import-source`, `export`, and `icons` read no settings. ## 3. How the file is found Each command looks for the settings of each file it reads, in this order, and uses the first it finds: | Order | Where | Use it for | |---|---|---| | 1 | `--config <file>` | One run with other settings, such as a stricter rule set in CI. | | 2 | The file `PLOTWORK_CONFIG` names | A CI job or a shell that always uses the same file. | | 3 | `plotwork.config.json`, then `.plotwork/config.json`, in the file's folder and each folder above it, up to the folder that holds `.git` | The usual case: one file at the root of the repository. | Because the search starts at each file, a repository of many packages can keep a settings file in a package's folder, and `sync "**/*.md"` applies the nearest one to each page. `generate` reads one file: the first found from the working folder up, and the paths in its `sources` start from that file's folder. A file that does not check stops the command with exit code 2, and each problem is printed with its line and column in the file. ## 4. Options and settings together | What | Order, first wins | |---|---| | A header option of a diagram, such as `direction` | The diagram's header, then `defaults`, then the built-in default | | The theme of `render` | `--theme`, then `defaults.theme`, then `light` | | Whether `render` embeds the source | `--no-embed-source`, then `defaults.embedSource`, then embedded | | The pictures `sync` and `generate` write | A source's own `schemes`, then `defaults.schemes`, then `light-and-dark` (a light and a dark SVG); `auto` writes one SVG that follows the reader's setting | | The rules of `check` | `--org` or `PLOTWORK_ORG`, then `rules` | A change to `defaults` changes the pictures of every diagram that leaves those options out, so `sync --check` reports them stale until `sync` redraws them. Change the file and run `sync` in the same commit. ## See also | Doc | Why | |---|---| | [Repository settings](https://docs.plotwork.dev/repository-settings.md) | Every field of the file, the rules, and sharing rules across repositories | | [Environment variables](https://docs.plotwork.dev/cli/environment.md) | The variables the command line reads | | [Diagrams as code](https://docs.plotwork.dev/diagrams-as-code.md) | The `sources` section and `generate` | | [Command reference](https://docs.plotwork.dev/cli/commands.md) | Every option of every command | --- # Command line in CI > Page: https://docs.plotwork.dev/cli/ci. Index of every page: https://docs.plotwork.dev/llms.txt The pipeline step that fails a build when a diagram picture is stale or a generated diagram has drifted, for GitHub Actions, GitLab CI, Azure Pipelines, Jenkins, and CircleCI. Each runs the pinned release with `npx`, so the job installs nothing beyond Node 22. ## TL;DR | Question | Answer | |---|---| | Which check do I run? | `sync --check` for the pictures under diagram fences. Add `generate --check` when diagrams are made from Terraform, OpenAPI or other files, and `check` when the repository has style rules. | | What fails the job? | Exit code 1 for a problem in a diagram, 2 for a mistake in the command. Both fail a step in every CI system. | | Does it need a key or a token? | No. Only `check --org` reads a key, from `PLOTWORK_API_KEY`. | | Does it send my diagrams anywhere? | No. The check reads the checked-out files and writes nothing. | | On GitHub, should I use this or the Action? | Either. The [GitHub Action](https://docs.plotwork.dev/diagrams-in-ci.md) runs the same engine without Node and adds a job summary. The command line is the same step on every other CI. | | How do I fix a failure? | Run `npx -y @buun_group/plotwork@0.1.0-beta.13 sync` on the same files, then commit the Markdown and the SVGs it wrote. | Every sample pins the release. Move the pin in a change you review, and run `sync` in the same change, so the pictures are drawn by the release the check runs. ## The commands | Step | Command | Fails when | |---|---|---| | Pictures | `npx -y @buun_group/plotwork@0.1.0-beta.13 sync --check "**/*.md"` | A picture is missing or stale, or a diagram has a mistake | | Generated diagrams | `npx -y @buun_group/plotwork@0.1.0-beta.13 generate --check` | A diagram drifted from the files it is made from | | Style rules | `npx -y @buun_group/plotwork@0.1.0-beta.13 check "**/*.md"` | A rule at level error is broken | `generate --check` exits 2 when the settings have no `sources`, so add its step only to a repository that makes diagrams from files. `check` exits 2 when there are no rules, in the same way. `"**/*.md"` never looks in `node_modules`, `.git`, or build output. Narrow it to the folders that hold diagrams, such as `README.md "docs/**/*.md"`, on a large repository. `generate --check --since <ref>` checks only the sources whose files changed since that ref, which keeps a large repository's check short. The job needs the history up to the ref: fetch it in the checkout step. ## GitHub Actions ```yaml name: diagrams on: pull_request: paths: ['**/*.md', '**/*.diagram', 'plotwork.config.json'] permissions: contents: read jobs: diagrams: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: persist-credentials: false - uses: actions/setup-node@v6 with: node-version: 22 - run: npx -y @buun_group/plotwork@0.1.0-beta.13 sync --check --format github "**/*.md" ``` `--format github` prints each stale fence and each mistake as an annotation on its line of the pull request. To report drift to code scanning, add a step that runs `generate --check --format sarif --out plotwork.sarif` with `if: always()`, then uploads the file with `github/codeql-action/upload-sarif`. ## GitLab CI ```yaml diagrams: image: node:22 rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" script: - npx -y @buun_group/plotwork@0.1.0-beta.13 sync --check "**/*.md" - npx -y @buun_group/plotwork@0.1.0-beta.13 generate --check --format junit --out plotwork-junit.xml artifacts: when: always reports: junit: plotwork-junit.xml ``` The JUnit report shows each generated diagram as a test in the merge request, failed when it drifted. ## Azure Pipelines ```yaml trigger: none pr: branches: include: [main] pool: vmImage: ubuntu-latest steps: - task: NodeTool@0 inputs: versionSpec: '22.x' - script: npx -y @buun_group/plotwork@0.1.0-beta.13 sync --check "**/*.md" displayName: Diagram pictures are current - script: npx -y @buun_group/plotwork@0.1.0-beta.13 generate --check --format junit --out $(Build.ArtifactStagingDirectory)/plotwork-junit.xml displayName: Generated diagrams have not drifted - task: PublishTestResults@2 condition: always() inputs: testResultsFormat: JUnit testResultsFiles: $(Build.ArtifactStagingDirectory)/plotwork-junit.xml ``` ## Jenkins ```groovy pipeline { agent { docker { image 'node:22' } } stages { stage('Diagrams') { steps { sh 'npx -y @buun_group/plotwork@0.1.0-beta.13 sync --check "**/*.md"' sh 'npx -y @buun_group/plotwork@0.1.0-beta.13 generate --check --format junit --out plotwork-junit.xml' } } } post { always { junit allowEmptyResults: true, testResults: 'plotwork-junit.xml' } } } ``` ## CircleCI ```yaml version: 2.1 jobs: diagrams: docker: - image: cimg/node:22.20.0 steps: - checkout - run: npx -y @buun_group/plotwork@0.1.0-beta.13 sync --check "**/*.md" - run: mkdir -p reports && npx -y @buun_group/plotwork@0.1.0-beta.13 generate --check --format junit --out reports/plotwork-junit.xml - store_test_results: path: reports workflows: diagrams: jobs: - diagrams ``` ## Other CI systems Any system that runs a command and reads its exit code runs the check: install Node 22, then run the command. Exit code 0 passes; 1 and 2 fail. For a runner with no access to the npm registry, mirror the package into your own registry or cache, as you would any other dependency. It is one file with no dependencies to fetch, and the checks make no request. ## See also | Doc | Why | |---|---| | [Keep diagrams in sync in CI](https://docs.plotwork.dev/diagrams-in-ci.md) | The GitHub Action, its inputs, and opening a pull request with the redrawn pictures | | [Diagrams as code](https://docs.plotwork.dev/diagrams-as-code.md) | Sources, overrides, and the drift check | | [Exit codes and output formats](https://docs.plotwork.dev/cli/exit-codes-and-output.md) | What `github`, `json`, `sarif`, and `junit` print | | [Enterprise rollout](https://docs.plotwork.dev/enterprise-rollout.md) | Reusable workflows, required checks, and air-gapped runners | --- # Exit codes and output formats > Page: https://docs.plotwork.dev/cli/exit-codes-and-output. Index of every page: https://docs.plotwork.dev/llms.txt What each exit code of the command line means, for every command, and what each `--format` prints. A script or a CI step reads the code; a person reads the text; a code scanner or a test view reads a report. ## TL;DR | Question | Answer | |---|---| | What are the exit codes? | `0` all is well, `1` a problem in a diagram, `2` a mistake in the command line. | | Why two failure codes? | A CI step fails on both, and a script can tell a diagram that needs fixing from a command that needs fixing. | | Where do mistakes go? | Standard error, each with its file, line, column, code, and a hint. Standard output holds the result: a source, a report, or a summary line. | | Which formats are there? | `text` (the default), `github`, `json`, `sarif`, and `junit`, each on the commands that take it. `export` has its own formats for wikis. | | Is the JSON report stable? | Yes. It carries a version, and its JSON Schema is published. A change that breaks a reader gets a new version. | ## Exit codes | Code | Meaning | Examples | |---|---|---| | `0` | All is well. | Every picture is current, every diagram is valid, the source was written. | | `1` | A problem in a diagram. | A stale or missing picture, a syntax error, a broken style rule, a generated diagram that drifted, a picture with no source to read. | | `2` | A mistake in the command line. | An unknown option, a missing operand, a glob that matches nothing, a settings file that does not check, `generate` with no `sources`. | Each command says what the codes mean for it: For every command, code `2` is a mistake in the command line: an unknown option, a missing operand, a file or glob that matches nothing, or a settings file that cannot be read. The last column says what else it means for that command. | Command | 0 | 1 | 2 | |---|---|---|---| | [`sync`](https://docs.plotwork.dev/cli/commands/sync.md) | Every picture is current (with `--check`), or every picture was written. | A picture is stale (with `--check`), or a diagram has a mistake and was not drawn. | Nothing more. | | [`validate`](https://docs.plotwork.dev/cli/commands/validate.md) | Every diagram is valid. | A diagram has an error, or a warning with `--strict`. | Nothing more. | | [`check`](https://docs.plotwork.dev/cli/commands/check.md) | No rule at level error is broken. | A rule at level error is broken, a diagram has an error, or the saved rules could not be read. | Also when no rules are found, or `--org` is given with no key. | | [`render`](https://docs.plotwork.dev/cli/commands/render.md) | The SVG was written. | The diagram has an error, or the file holds no diagram. | Nothing more. | | [`read`](https://docs.plotwork.dev/cli/commands/read.md) | The source was printed or written. | The picture carries no source, the source has errors, or a hosted link could not be read. | Nothing more. | | [`outline`](https://docs.plotwork.dev/cli/commands/outline.md) | Every diagram was outlined. | A diagram has an error. | Nothing more. | | [`generate`](https://docs.plotwork.dev/cli/commands/generate.md) | Every diagram is in step (with `--check`), or every diagram was written. | A diagram drifted (with `--check`), or a diagram has an error. | Also when there are no settings, no sources, or `--since` names a ref git does not know. | | [`import-source`](https://docs.plotwork.dev/cli/commands/import-source.md) | The source was printed or written. | Nothing could be imported from the file. | Also when the format or a value of `--view`, `--chart`, or `--group-by` is not one it knows. | | [`import`](https://docs.plotwork.dev/cli/commands/import.md) | Everything converted. | A diagram or a fence could not be converted, or the file could not be read. | Also when the file is not `.mmd`, `.mermaid`, or Markdown. | | [`export`](https://docs.plotwork.dev/cli/commands/export.md) | The text was printed or written. | The diagram has an error. | Also when `--format` is missing or `--image` is not an https address. | | [`publish`](https://docs.plotwork.dev/cli/commands/publish.md) | Every diagram was saved, and its link printed. | A diagram has a mistake (nothing is sent), the API refused one (its code and words are printed), or the API could not be reached. | Also when `PLOTWORK_API_KEY` is missing or refused, `--personal` and `--org` are both given, or `--alias` is given for a file with several diagrams. | | [`icons`](https://docs.plotwork.dev/cli/commands/icons.md) | The search ran, whether or not anything matched. | Not used. | No query was given. | ## Output formats | Format | Commands | What it prints | |---|---|---| | `text` | `sync`, `check`, `generate`, `import-source`, `import` | Lines a person reads. Mistakes go to standard error with a line, a column, and a hint. | | `github` | `sync`, `check`, `generate` | GitHub workflow commands: each problem is an `::error` or `::warning` annotation on its file and line in the pull request. | | `json` | `generate`, `import-source`, `import` | One JSON document on standard output, for a script. `generate` writes its versioned report, whose JSON Schema is at `https://docs.plotwork.dev/schemas/plotwork.report.json`. | | `sarif` | `generate` | A SARIF 2.1.0 report for code scanning, such as GitHub code scanning or Azure DevOps. | | `junit` | `generate` | A JUnit XML report: one test case for each source, failed when its diagram drifted or has an error, for the test view of GitLab, Jenkins, CircleCI, or Azure Pipelines. | | `confluence` | `export` | Confluence storage format (XHTML): the format of Confluence's REST API and of a page's source in Data Center. | | `confluence-wiki` | `export` | Confluence wiki markup, which the Cloud editor takes through Insert, then Markup. | | `notion` | `export` | Markdown that Notion turns into an image block and a code block when it is pasted. | ### text The default. Each mistake is printed as the engine reports it: the code, the message, the line of the diagram with a caret under the column, and a hint. Under it, `at file line` names the line in the file, so an editor can jump to it. The last line is a summary, such as `up to date: README.md` or `ok: 3 diagram(s) checked, 0 drifted, 0 error(s)`. ### github [GitHub workflow commands](https://docs.github.com/actions/reference/workflow-commands-for-github-actions). A line such as the one below becomes an error on line 12 of the file in the pull request's Files changed tab: ```text ::error file=docs/architecture.md,line=12,title=Stale diagram::The picture under this diagram is missing or out of date. ``` The file is named from the working directory, which in a workflow is the repository root. Run the command from the root, or the annotations will not find their files. ### json One JSON document on standard output and nothing else, so it can be piped to `jq`. `import` and `import-source` print the conversion with its diagnostics. `generate` prints its versioned report: each diagram, whether it drifted, what changed by meaning, its CODEOWNERS owners, and where the run happened. The schema is at [plotwork.report.json](https://docs.plotwork.dev/schemas/plotwork.report.json). ### sarif and junit Reports from `generate` for tools that already show findings. SARIF goes to code scanning, where a drifted diagram is an alert on its file. JUnit goes to a test view: each source is a test case that fails when its diagram drifted or has an error. With `--out <file>` the report is written to the file and the terminal gets the text a person reads, so the log still says what drifted. ## See also | Doc | Why | |---|---| | [CI usage](https://docs.plotwork.dev/cli/ci.md) | Each format in a pipeline | | [Command reference](https://docs.plotwork.dev/cli/commands.md) | Every command and its options | | [Troubleshooting](https://docs.plotwork.dev/cli/troubleshooting.md) | What to do about a failure | --- # Environment variables > Page: https://docs.plotwork.dev/cli/environment. Index of every page: https://docs.plotwork.dev/llms.txt Every environment variable the command line reads, and the ones it ignores. A flag on the command line always wins over a variable. ## TL;DR | Question | Answer | |---|---| | Do I need to set anything? | No. Every command runs with no variable set. | | Which variable do I set most? | `PLOTWORK_CONFIG`, to name a settings file, and `PLOTWORK_ORG` with `PLOTWORK_API_KEY` to check against an organisation's saved rules. | | Is a key ever printed? | No. `PLOTWORK_API_KEY` is sent only to read the rules and never appears in the output or a report. | | Do `PLOTWORK_USAGE` and `DO_NOT_TRACK` matter? | Not here. The command line counts nothing and sends nothing whatever they say. They control the local MCP server. | ## The variables it reads | Variable | Read by | What it does | |---|---|---| | `PLOTWORK_CONFIG` | `sync`, `validate`, `check`, `render`, `outline`, `generate`, `publish` | The settings file to read when `--config` is not given, instead of looking for `plotwork.config.json` or `.plotwork/config.json`. | | `PLOTWORK_ORG` | `check` | The organisation whose saved style rules to check against when `--org` is not given. | | `PLOTWORK_API_KEY` | `check`, `publish` | An API key: for `check`, of the organisation whose rules are read; for `publish`, the key the diagrams are saved with. Never printed. | | `PLOTWORK_API_URL` | `check`, `publish` | The API the rules are read from and diagrams are published to, instead of `https://api.plotwork.dev`. | | `PLOTWORK_VENDOR_ICONS` | `sync`, `render`, `export`, `generate` | `off` draws no vendor artwork in this run: a product icon becomes a plain shape. | | `GITHUB_REPOSITORY`, `GITHUB_SHA`, `GITHUB_REF`, `GITHUB_RUN_ID`, `GITHUB_SERVER_URL`, `GITHUB_EVENT_NAME` | `generate` | Read only to name the run in a `json` report, when GitHub Actions sets them. | ## The variables it ignores | Variable | Why | |---|---| | `PLOTWORK_USAGE` | Turns on the local MCP server's opt-in usage counts. The command line keeps no counts, so there is nothing to turn on. See [Usage counts and sharing](https://docs.plotwork.dev/mcp/usage.md). | | `DO_NOT_TRACK` | Stops the local MCP server sharing its totals. The command line shares nothing. | | `PLOTWORK_API_URL` outside `check` | Only `check --org` talks to the API. `read` fetches a hosted link from the address you give it. | | `NO_COLOR`, `FORCE_COLOR` | The output has no colour to turn off. | ## Set them in CI Put a key in your CI system's secret store and pass it to the one step that needs it: ```yaml - run: npx -y @buun_group/plotwork@0.1.0-beta.13 check --org acme --format github "**/*.md" env: PLOTWORK_API_KEY: ${{ secrets.PLOTWORK_API_KEY }} ``` Use an API key of the organisation, created in the organisation's keys in the [portal](https://app.plotwork.dev), so the check keeps working when the person who set it up leaves. Saving rules in an organisation is part of Pro; see [Repository settings](https://docs.plotwork.dev/repository-settings.md). ## See also | Doc | Why | |---|---| | [Configuration](https://docs.plotwork.dev/cli/configuration.md) | How the settings file is found | | [Repository settings](https://docs.plotwork.dev/repository-settings.md) | Sharing rules across repositories with an organisation | | [Privacy policy](https://docs.plotwork.dev/privacy-policy.md) | What Plotwork keeps, and what it never receives | --- # Command line troubleshooting > Page: https://docs.plotwork.dev/cli/troubleshooting. Index of every page: https://docs.plotwork.dev/llms.txt What a message from the command line means and what to do about it. Most failures are one of the rows below. ## TL;DR | Question | Answer | |---|---| | The check says a picture is stale, but I changed nothing | The pictures were drawn by another release or with other settings. Run `sync` with the release the check pins, and commit what it writes. | | A glob matches nothing | Quote it, such as `"docs/**/*.md"`, and run from the repository root. | | It says my Node is too old | Install Node 22 or newer. | | Where do I see every option? | `plotwork <command> --help`, or the [Command reference](https://docs.plotwork.dev/cli/commands.md). | ## Stale pictures and drift | Message | Cause | Fix | |---|---|---| | `stale: README.md needs sync` | The source of a fence changed and its picture did not, or the picture was drawn by another release or with other defaults. | Run `sync` on the file with the same release and settings as the check, and commit the Markdown and the SVGs. | | `stale` in CI, `up to date` on your machine | The check pins one release and your machine ran another, or CI found a different settings file. | Run the exact command of the CI step, with its pinned version, from the repository root. | | `drift: docs/infra.diagram` | The files a generated diagram is made from changed. The lines under it name each node and edge added, removed, or changed. | Run `generate` and commit what it writes. Change the diagram in its overrides file, never in the generated file. See [Diagrams as code](https://docs.plotwork.dev/diagrams-as-code.md). | | A hand edit of a generated diagram is undone | `generate` writes over the generated file. | Run `generate --capture` after the edit, which moves it to the overrides file, then `generate`. | ## Usage mistakes, exit code 2 | Message | Cause | Fix | |---|---|---| | `no Markdown file matches docs/*.md` | The glob matched nothing from the working directory, or the shell expanded it first. | Quote the glob, and check the folder you run in. | | `Unknown option '--colour'` | The option is not one the command takes. The help follows the message. | Run `plotwork <command> --help` for the options. | | `no settings: add plotwork.config.json with a sources section` | `generate` found no settings file. | Add the file, or pass `--config <file>`. | | `no rules: add a rules section to plotwork.config.json` | `check` found no rules. | Add a `rules` section, or pass `--org` with `PLOTWORK_API_KEY`. | | A settings error with a line and a caret | The settings file does not check against its schema. | Fix the line it names. Add `$schema` to the file so your editor shows the mistake as you type. | | `generate --out writes a report, so it needs --format json, sarif, or junit` | `--out` was given for text or github output. | Add a report format, or leave out `--out`. | ## Diagram mistakes, exit code 1 Each mistake has a code, a line and column, and a hint. The hint is the fix: apply it and run the command again. Every code is listed in [Diagnostics](https://docs.plotwork.dev/reference/language/diagnostics.md). `validate --strict` also fails on warnings, such as a label too long to read; leave out `--strict` to report them without failing. ## Installing and running | Symptom | Cause | Fix | |---|---|---| | `plotwork needs Node 22 or newer` | An older Node runs the command. | Install Node 22 or newer, or use the `node:22` image in CI. | | `npx` asks to install the package | `-y` is missing. | Add `-y` (or `--yes`) after `npx`, as every sample does. | | `npm error 404` for the version | The version is not on npm, or a private registry does not mirror it. | Check the version on [npm](https://www.npmjs.com/package/@buun_group/plotwork), and mirror it into your registry. | | A product icon is drawn as a plain shape | `PLOTWORK_VENDOR_ICONS=off` is set. | Unset it. See [Environment variables](https://docs.plotwork.dev/cli/environment.md). | ## Reading a picture | Message | Cause | Fix | |---|---|---| | The picture carries no source | It was drawn with `--no-embed-source`, or by a tool that leaves the source out. `read` prints its title and labels instead. | Draw it again with the source embedded, or write the source again from the labels. See [Editing an existing diagram](https://docs.plotwork.dev/editing-existing-diagrams.md). | | A hosted link cannot be read | The link is wrong, deleted, or private to an organisation. | Open the link in a browser to check it. | ## See also | Doc | Why | |---|---| | [Exit codes and output formats](https://docs.plotwork.dev/cli/exit-codes-and-output.md) | What each code means for each command | | [CI usage](https://docs.plotwork.dev/cli/ci.md) | The step for each CI system | | [Configuration](https://docs.plotwork.dev/cli/configuration.md) | How the settings file is found | --- # Command reference > Page: https://docs.plotwork.dev/cli/commands. Index of every page: https://docs.plotwork.dev/llms.txt Every command of the Plotwork command line, `plotwork`, with a page for each. The pages say the same as `plotwork <command> --help`, which prints the options, defaults, examples, and exit codes of one command in a terminal. ## TL;DR | Question | Answer | |---|---| | How do I run a command? | `npx -y @buun_group/plotwork@0.1.0-beta.13 <command>`, with no install, or `plotwork <command>` once the package is installed. See [Overview](https://docs.plotwork.dev/cli/overview.md). | | Which command keeps pictures in step? | [`plotwork sync`](https://docs.plotwork.dev/cli/commands/sync.md), and `plotwork sync --check` in CI. | | Which commands read the repository settings? | `sync`, `validate`, `check`, `render`, `outline`, `generate`, `publish`. See [Configuration](https://docs.plotwork.dev/cli/configuration.md). | | What do the exit codes mean? | `0` all is well, `1` a problem in a diagram, `2` a mistake in the command line. | | Where is the help in a terminal? | `plotwork --help` lists every command; `plotwork <command> --help` prints one. | ## Commands | Command | What it does | |---|---| | [`plotwork sync`](https://docs.plotwork.dev/cli/commands/sync.md) | Draw the pictures under every diagram fence of Markdown files; `--check` fails when one is stale | | [`plotwork validate`](https://docs.plotwork.dev/cli/commands/validate.md) | Check every diagram of a Markdown or `.diagram` file; `--strict` also fails on warnings | | [`plotwork check`](https://docs.plotwork.dev/cli/commands/check.md) | Check every diagram against a team's style rules; fails on a broken rule at level error | | [`plotwork render`](https://docs.plotwork.dev/cli/commands/render.md) | Draw the first diagram of a file, or a view link, to an SVG that carries its source | | [`plotwork read`](https://docs.plotwork.dev/cli/commands/read.md) | Print the source a Plotwork SVG, a view link, or a hosted link carries, to edit it | | [`plotwork outline`](https://docs.plotwork.dev/cli/commands/outline.md) | Print a plain-text outline of every diagram, to check what it says | | [`plotwork generate`](https://docs.plotwork.dev/cli/commands/generate.md) | Make the diagrams the settings' sources name from their files; `--check` fails on drift | | [`plotwork import-source`](https://docs.plotwork.dev/cli/commands/import-source.md) | Turn SQL, OpenAPI, Terraform, Compose, CSV, a git log, and more into Plotwork source | | [`plotwork import`](https://docs.plotwork.dev/cli/commands/import.md) | Convert Mermaid to Plotwork: a `.mmd` file, or every `mermaid` fence of a Markdown file | | [`plotwork export`](https://docs.plotwork.dev/cli/commands/export.md) | Print paste-ready text of the first diagram for Confluence or Notion | | [`plotwork publish`](https://docs.plotwork.dev/cli/commands/publish.md) | Save every diagram of a `.diagram` or Markdown file to Plotwork, where your organisations' Publishing settings decide, and print the link | | [`plotwork icons`](https://docs.plotwork.dev/cli/commands/icons.md) | Search the bundled icons by name | ## See also | Doc | Why | |---|---| | [Quick start](https://docs.plotwork.dev/cli/quick-start.md) | The first diagram, from install to a checked pull request | | [Exit codes and output formats](https://docs.plotwork.dev/cli/exit-codes-and-output.md) | What every code and format means | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The same operations for an AI agent, over MCP | --- # plotwork sync > Page: https://docs.plotwork.dev/cli/commands/sync. Index of every page: https://docs.plotwork.dev/llms.txt Draw the pictures under every diagram fence of Markdown files; `--check` fails when one is stale. It is a command of the Plotwork command line, the npm package `@buun_group/plotwork`. ## TL;DR | Question | Answer | |---|---| | What does it do? | Draw the pictures under every diagram fence of Markdown files; `--check` fails when one is stale. | | How do I run it? | `npx -y @buun_group/plotwork@0.1.0-beta.13 sync`, or `plotwork sync` once the package is installed. | | What does it write? | The Markdown files and their SVGs, unless `--check` is given. | | Does it use the network? | No. It reads and writes files on this machine only. | | Does it read the repository settings? | Yes: the header defaults of the settings file apply to every diagram. See [Configuration](https://docs.plotwork.dev/cli/configuration.md). | | How do I read this in a terminal? | `plotwork sync --help` | ## Synopsis ```sh plotwork sync <files...> [--check] [--assets <dir>] [--prefix <text>] [--format text|github] [--config <file>] ``` ## What it does Draws every `diagram` fence of the Markdown files as a light and a dark SVG, and writes the picture block under the fence, so the diagram shows on GitHub, GitLab, and any Markdown viewer. A fence whose picture is already current is left as it is. Each word is a Markdown file, a folder (every `.md` file under it), or a quoted glob such as `"docs/**/*.md"`. The command expands the glob itself, so it means the same in every shell, and never looks in `node_modules`, `.git`, or build output. With `--check` it writes nothing and exits 1 when any picture is missing or stale, or any diagram has a mistake. That is the check to run in CI. ## Options | Option | Default | What it does | |---|---|---| | `--check` | Off | Write nothing; exit 1 when a picture is missing or stale, or a diagram has a mistake. | | `--assets <dir>` | `assets/diagrams` | The folder the SVGs are written to, relative to each Markdown file. | | `--prefix <text>` | the Markdown file name | The start of each SVG file name and picture id. | | `--format text\|github` | `text` | `github` prints each stale fence and each mistake as a GitHub annotation on its line. | | `--config <file>` | `PLOTWORK_CONFIG`, else the nearest `plotwork.config.json` or `.plotwork/config.json` from the file up to the repository root | The repository settings file to read: header defaults, and the style rules check applies. | ## Examples Draw every diagram of the README and the docs folder, and write the pictures. ```sh plotwork sync README.md "docs/**/*.md" ``` Fail when any picture is stale, and write nothing. ```sh plotwork sync --check "docs/**/*.md" ``` The same in a GitHub workflow, with an annotation on each stale fence. ```sh plotwork sync --check --format github "**/*.md" ``` ## Exit codes | Code | When | |---|---| | `0` | Every picture is current (with `--check`), or every picture was written. | | `1` | A picture is stale (with `--check`), or a diagram has a mistake and was not drawn. | | `2` | A mistake in the command line: an unknown option, a missing operand, a file or glob that matches nothing, or a settings file that cannot be read. | ## See also | Doc | Why | |---|---| | [Command reference](https://docs.plotwork.dev/cli/commands.md) | Every command of the command line | | [Markdown and GitHub](https://docs.plotwork.dev/markdown-and-github.md) | How a fence and its picture block look in a page | | [CI usage](https://docs.plotwork.dev/cli/ci.md) | The check in every CI system | | [Exit codes and output formats](https://docs.plotwork.dev/cli/exit-codes-and-output.md) | What every code and format means | --- # plotwork validate > Page: https://docs.plotwork.dev/cli/commands/validate. Index of every page: https://docs.plotwork.dev/llms.txt Check every diagram of a Markdown or `.diagram` file; `--strict` also fails on warnings. It is a command of the Plotwork command line, the npm package `@buun_group/plotwork`. ## TL;DR | Question | Answer | |---|---| | What does it do? | Check every diagram of a Markdown or `.diagram` file; `--strict` also fails on warnings. | | How do I run it? | `npx -y @buun_group/plotwork@0.1.0-beta.13 validate`, or `plotwork validate` once the package is installed. | | What does it write? | Nothing. | | Does it use the network? | No. It reads and writes files on this machine only. | | Does it read the repository settings? | Yes: the header defaults of the settings file apply to every diagram. See [Configuration](https://docs.plotwork.dev/cli/configuration.md). | | How do I read this in a terminal? | `plotwork validate --help` | ## Synopsis ```sh plotwork validate <file> [--strict] [--config <file>] ``` ## What it does Parses every diagram of one file: each `diagram` fence of a Markdown file, or the whole of a `.diagram` file. Each mistake is printed with its line and column in the file, the code, and a hint that says how to fix it. Warnings, such as a label too long to read, are printed and pass unless `--strict` is given. ## Options | Option | Default | What it does | |---|---|---| | `--strict` | Off | Exit 1 on warnings as well as errors. | | `--config <file>` | `PLOTWORK_CONFIG`, else the nearest `plotwork.config.json` or `.plotwork/config.json` from the file up to the repository root | The repository settings file to read: header defaults, and the style rules check applies. | ## Examples Check every diagram fence of a Markdown file. ```sh plotwork validate docs/architecture.md ``` Fail on warnings too. ```sh plotwork validate checkout.diagram --strict ``` ## Exit codes | Code | When | |---|---| | `0` | Every diagram is valid. | | `1` | A diagram has an error, or a warning with `--strict`. | | `2` | A mistake in the command line: an unknown option, a missing operand, a file or glob that matches nothing, or a settings file that cannot be read. | ## See also | Doc | Why | |---|---| | [Command reference](https://docs.plotwork.dev/cli/commands.md) | Every command of the command line | | [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md) | The language the errors point at | | [Language reference](https://docs.plotwork.dev/reference/language.md) | Every statement and option | | [Exit codes and output formats](https://docs.plotwork.dev/cli/exit-codes-and-output.md) | What every code and format means | --- # plotwork check > Page: https://docs.plotwork.dev/cli/commands/check. Index of every page: https://docs.plotwork.dev/llms.txt Check every diagram against a team's style rules; fails on a broken rule at level error. It is a command of the Plotwork command line, the npm package `@buun_group/plotwork`. ## TL;DR | Question | Answer | |---|---| | What does it do? | Check every diagram against a team's style rules; fails on a broken rule at level error. | | How do I run it? | `npx -y @buun_group/plotwork@0.1.0-beta.13 check`, or `plotwork check` once the package is installed. | | What does it write? | Nothing. | | Does it use the network? | No, unless `--org` or `PLOTWORK_ORG` names an organisation: then one request to the Plotwork API reads its rules, with the key in `PLOTWORK_API_KEY`. | | Does it read the repository settings? | Yes: the header defaults of the settings file apply to every diagram. See [Configuration](https://docs.plotwork.dev/cli/configuration.md). | | How do I read this in a terminal? | `plotwork check --help` | ## Synopsis ```sh plotwork check <files...> [--org <slug>] [--format text|github] [--config <file>] ``` ## What it does Checks every diagram of the files against style rules: required icons, naming patterns, allowed tones, a size budget, and forbidden kinds. The rules are the `rules` section of the repository settings file, or with `--org` the rules an organisation saved in Plotwork. Each word is a Markdown or `.diagram` file, a folder, or a quoted glob. The header defaults of the settings are applied first, so a diagram is checked as it is drawn. ## Options | Option | Default | What it does | |---|---|---| | `--org <slug>` | `PLOTWORK_ORG`, else the rules of the settings file | Check against the rules an organisation saved instead, read with the key in `PLOTWORK_API_KEY`. | | `--format text\|github` | `text` | `github` prints each broken rule as a GitHub annotation on its line. | | `--config <file>` | `PLOTWORK_CONFIG`, else the nearest `plotwork.config.json` or `.plotwork/config.json` from the file up to the repository root | The repository settings file to read: header defaults, and the style rules check applies. | ## Examples Check the docs and every diagram file under diagrams against the rules of the settings file. ```sh plotwork check "docs/**/*.md" diagrams ``` Check against the organisation's saved rules in a GitHub workflow. ```sh plotwork check --org acme --format github "**/*.md" ``` ## Exit codes | Code | When | |---|---| | `0` | No rule at level error is broken. | | `1` | A rule at level error is broken, a diagram has an error, or the saved rules could not be read. | | `2` | A mistake in the command line: an unknown option, a missing operand, a file or glob that matches nothing, or a settings file that cannot be read. Also when no rules are found, or `--org` is given with no key. | ## See also | Doc | Why | |---|---| | [Command reference](https://docs.plotwork.dev/cli/commands.md) | Every command of the command line | | [Repository settings](https://docs.plotwork.dev/repository-settings.md) | The rules section and its checks | | [Organisations](https://docs.plotwork.dev/organisations.md) | Rules saved for a whole organisation | | [Exit codes and output formats](https://docs.plotwork.dev/cli/exit-codes-and-output.md) | What every code and format means | --- # plotwork render > Page: https://docs.plotwork.dev/cli/commands/render. Index of every page: https://docs.plotwork.dev/llms.txt Draw the first diagram of a file, or a view link, to an SVG that carries its source. It is a command of the Plotwork command line, the npm package `@buun_group/plotwork`. ## TL;DR | Question | Answer | |---|---| | What does it do? | Draw the first diagram of a file, or a view link, to an SVG that carries its source. | | How do I run it? | `npx -y @buun_group/plotwork@0.1.0-beta.13 render`, or `plotwork render` once the package is installed. | | What does it write? | One SVG. | | Does it use the network? | No. A view link is decoded on this machine. | | Does it read the repository settings? | Yes: the header defaults of the settings file apply to every diagram. See [Configuration](https://docs.plotwork.dev/cli/configuration.md). | | How do I read this in a terminal? | `plotwork render --help` | ## Synopsis ```sh plotwork render <file|link> [--out <svg>] [--theme light|dark|auto] [--no-embed-source] [--config <file>] ``` ## What it does Draws the first diagram of a Markdown or `.diagram` file to one SVG. The source is written into the SVG, so `read` can give it back later; `--no-embed-source` leaves it out. The file can also be a `viewUrl`, `svgUrl`, or `pngUrl` a Plotwork tool returned. Quote it: the diagram is decoded from the link on this machine and never fetched. ## Options | Option | Default | What it does | |---|---|---| | `--out <svg>` | the file name with `.svg`, or `diagram.svg` for a link | The SVG to write. | | `--theme light\|dark\|auto` | the theme of the settings, else `light` | The colours to draw in; `auto` draws one picture that follows the reader. | | `--no-embed-source` | the source is embedded | Leave the source out of the SVG. | | `--config <file>` | `PLOTWORK_CONFIG`, else the nearest `plotwork.config.json` or `.plotwork/config.json` from the file up to the repository root | The repository settings file to read: header defaults, and the style rules check applies. | ## Examples Draw a diagram file to an SVG. ```sh plotwork render checkout.diagram --out checkout.svg ``` Draw it in the dark theme. ```sh plotwork render checkout.diagram --theme dark --out checkout.dark.svg ``` Write the picture of a view link a tool returned, with no request. ```sh plotwork render "<viewUrl>" --out flow.svg ``` ## Exit codes | Code | When | |---|---| | `0` | The SVG was written. | | `1` | The diagram has an error, or the file holds no diagram. | | `2` | A mistake in the command line: an unknown option, a missing operand, a file or glob that matches nothing, or a settings file that cannot be read. | ## See also | Doc | Why | |---|---| | [Command reference](https://docs.plotwork.dev/cli/commands.md) | Every command of the command line | | [Viewing diagrams](https://docs.plotwork.dev/viewing-diagrams.md) | The view links a tool returns | | [Editing an existing diagram](https://docs.plotwork.dev/editing-existing-diagrams.md) | Why the source is embedded | | [Exit codes and output formats](https://docs.plotwork.dev/cli/exit-codes-and-output.md) | What every code and format means | --- # plotwork read > Page: https://docs.plotwork.dev/cli/commands/read. Index of every page: https://docs.plotwork.dev/llms.txt Print the source a Plotwork SVG, a view link, or a hosted link carries, to edit it. It is a command of the Plotwork command line, the npm package `@buun_group/plotwork`. ## TL;DR | Question | Answer | |---|---| | What does it do? | Print the source a Plotwork SVG, a view link, or a hosted link carries, to edit it. | | How do I run it? | `npx -y @buun_group/plotwork@0.1.0-beta.13 read`, or `plotwork read` once the package is installed. | | What does it write? | The source, only with `--out`. | | Does it use the network? | Only for a hosted link: one request for that picture. A file or a view link is read on this machine. | | Does it read the repository settings? | No. | | How do I read this in a terminal? | `plotwork read --help` | ## Synopsis ```sh plotwork read <file.svg|link> [--out <file>] ``` ## What it does Prints the source a Plotwork picture carries, so the diagram can be changed and drawn again. The target is an SVG file, a view link a tool returned, or a hosted link such as `https://cdn.plotwork.dev/d/<token>.svg`. The source goes to standard output, or to `--out`, and everything else to standard error, so `plotwork read picture.svg > picture.diagram` writes only the source. A picture with no embedded source prints what can be read from it, such as its title and labels, and exits 1. ## Options | Option | Default | What it does | |---|---|---| | `--out <file>` | standard output | Write the source to a file. | ## Examples Get the source back from a picture in the repository. ```sh plotwork read docs/assets/diagrams/flow.svg > flow.diagram ``` Get the source of a hosted picture. ```sh plotwork read https://cdn.plotwork.dev/d/<token>.svg --out flow.diagram ``` ## Exit codes | Code | When | |---|---| | `0` | The source was printed or written. | | `1` | The picture carries no source, the source has errors, or a hosted link could not be read. | | `2` | A mistake in the command line: an unknown option, a missing operand, a file or glob that matches nothing, or a settings file that cannot be read. | ## See also | Doc | Why | |---|---| | [Command reference](https://docs.plotwork.dev/cli/commands.md) | Every command of the command line | | [Editing an existing diagram](https://docs.plotwork.dev/editing-existing-diagrams.md) | Change a picture you already have | | [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md) | The links read fetches | | [Exit codes and output formats](https://docs.plotwork.dev/cli/exit-codes-and-output.md) | What every code and format means | --- # plotwork outline > Page: https://docs.plotwork.dev/cli/commands/outline. Index of every page: https://docs.plotwork.dev/llms.txt Print a plain-text outline of every diagram, to check what it says. It is a command of the Plotwork command line, the npm package `@buun_group/plotwork`. ## TL;DR | Question | Answer | |---|---| | What does it do? | Print a plain-text outline of every diagram, to check what it says. | | How do I run it? | `npx -y @buun_group/plotwork@0.1.0-beta.13 outline`, or `plotwork outline` once the package is installed. | | What does it write? | Nothing. | | Does it use the network? | No. It reads and writes files on this machine only. | | Does it read the repository settings? | Yes: the header defaults of the settings file apply to every diagram. See [Configuration](https://docs.plotwork.dev/cli/configuration.md). | | How do I read this in a terminal? | `plotwork outline --help` | ## Synopsis ```sh plotwork outline <file> [--config <file>] ``` ## What it does Prints every diagram of a file as plain text: its nodes, groups, and connections in words. Use it to check that a diagram says what it should without looking at the picture, or to describe it in a review. ## Options | Option | Default | What it does | |---|---|---| | `--config <file>` | `PLOTWORK_CONFIG`, else the nearest `plotwork.config.json` or `.plotwork/config.json` from the file up to the repository root | The repository settings file to read: header defaults, and the style rules check applies. | ## Examples Describe every diagram of a page. ```sh plotwork outline docs/architecture.md ``` ## Exit codes | Code | When | |---|---| | `0` | Every diagram was outlined. | | `1` | A diagram has an error. | | `2` | A mistake in the command line: an unknown option, a missing operand, a file or glob that matches nothing, or a settings file that cannot be read. | ## See also | Doc | Why | |---|---| | [Command reference](https://docs.plotwork.dev/cli/commands.md) | Every command of the command line | | [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md) | What a diagram says, in words | | [Exit codes and output formats](https://docs.plotwork.dev/cli/exit-codes-and-output.md) | What every code and format means | --- # plotwork generate > Page: https://docs.plotwork.dev/cli/commands/generate. Index of every page: https://docs.plotwork.dev/llms.txt Make the diagrams the settings' sources name from their files; `--check` fails on drift. It is a command of the Plotwork command line, the npm package `@buun_group/plotwork`. ## TL;DR | Question | Answer | |---|---| | What does it do? | Make the diagrams the settings' sources name from their files; `--check` fails on drift. | | How do I run it? | `npx -y @buun_group/plotwork@0.1.0-beta.13 generate`, or `plotwork generate` once the package is installed. | | What does it write? | The diagrams and their pictures, unless `--check` is given; a report with `--out`. | | Does it use the network? | No. `--since` runs git on this machine, which reads the history and fetches nothing. | | Does it read the repository settings? | Yes: the header defaults of the settings file apply to every diagram. See [Configuration](https://docs.plotwork.dev/cli/configuration.md). | | How do I read this in a terminal? | `plotwork generate --help` | ## Synopsis ```sh plotwork generate [--check] [--capture] [--source <name>] [--since <ref>] [--format text|github|json|sarif|junit] [--out <file>] [--config <file>] ``` ## What it does Makes each diagram the `sources` of the settings file name from files of the repository (Terraform, OpenAPI, Compose, Kubernetes, Prisma, SQL, workflows, and more), applies its overrides file, and writes the `.diagram` file or the Markdown fence with its pictures. With `--check` it writes nothing and exits 1 when a diagram has drifted from its files, naming each node and edge that was added, removed, or changed. `--capture` first keeps hand edits of a generated diagram in its overrides file, so the next run does not undo them. Everything runs on this machine: nothing is fetched and no code in the files is run. `--since` asks git which files changed. ## Options | Option | Default | What it does | |---|---|---| | `--check` | Off | Write nothing; exit 1 when a diagram drifted from its files. | | `--capture` | Off | Keep hand edits of each generated diagram in its overrides file first. | | `--source <name>` | every source | Only the source of this name. | | `--since <ref>` | every source | Only the sources whose files changed since a git ref, such as `origin/main`. | | `--format text\|github\|json\|sarif\|junit` | `text` | `github` prints annotations; `json`, `sarif`, and `junit` print a report. | | `--out <file>` | standard output | Write the `json`, `sarif`, or `junit` report to a file, and print the text a person reads. | | `--config <file>` | `PLOTWORK_CONFIG`, else the nearest `plotwork.config.json` or `.plotwork/config.json` from the file up to the repository root | The repository settings file to read: header defaults, and the style rules check applies. | ## Examples Make every diagram the settings name, and write them. ```sh plotwork generate ``` Fail when a diagram whose files changed on this branch has drifted. ```sh plotwork generate --check --since origin/main ``` Write a report for code scanning, and print the summary. ```sh plotwork generate --check --format sarif --out plotwork.sarif ``` ## Exit codes | Code | When | |---|---| | `0` | Every diagram is in step (with `--check`), or every diagram was written. | | `1` | A diagram drifted (with `--check`), or a diagram has an error. | | `2` | A mistake in the command line: an unknown option, a missing operand, a file or glob that matches nothing, or a settings file that cannot be read. Also when there are no settings, no sources, or `--since` names a ref git does not know. | ## See also | Doc | Why | |---|---| | [Command reference](https://docs.plotwork.dev/cli/commands.md) | Every command of the command line | | [Diagrams as code](https://docs.plotwork.dev/diagrams-as-code.md) | Sources, overrides, and drift in full | | [CI usage](https://docs.plotwork.dev/cli/ci.md) | The drift check in every CI system | | [Repository settings](https://docs.plotwork.dev/repository-settings.md) | The sources section | | [Exit codes and output formats](https://docs.plotwork.dev/cli/exit-codes-and-output.md) | What every code and format means | --- # plotwork import-source > Page: https://docs.plotwork.dev/cli/commands/import-source. Index of every page: https://docs.plotwork.dev/llms.txt Turn SQL, OpenAPI, Terraform, Compose, CSV, a git log, and more into Plotwork source. It is a command of the Plotwork command line, the npm package `@buun_group/plotwork`. ## TL;DR | Question | Answer | |---|---| | What does it do? | Turn SQL, OpenAPI, Terraform, Compose, CSV, a git log, and more into Plotwork source. | | How do I run it? | `npx -y @buun_group/plotwork@0.1.0-beta.13 import-source`, or `plotwork import-source` once the package is installed. | | What does it write? | The source, only with `--out`. | | Does it use the network? | No. It reads and writes files on this machine only. | | Does it read the repository settings? | No. | | How do I read this in a terminal? | `plotwork import-source --help` | ## Synopsis ```sh plotwork import-source <format> <file|folder> [--out <file>] [--title <text>] [--format text|json] [--view endpoints|map|sequence|git|timeline|context|container|component] [--operation <id>] [--chart auto|xychart|pie|sankey|treemap|matrix] [--tables <names>] [--tags <tags>] [--paths <prefixes>] [--group-by module|network|file|owner] [--var-file <files>] [--root <folder>] ``` ## What it does Reads a file of another format, or every file of a folder, and prints a Plotwork diagram of it. The formats are sql, prisma, drizzle, openapi, terraform, terraform-plan, bicep, arm, pulumi, cloudformation, cdk, compose, kubernetes, helm, kustomize, backstage, structurizr, asyncapi, github-actions, gitlab-ci, azure-pipelines, csv, json, tree, git-log. Every diagnostic is printed with the line of the file it points at, and says what was left out. A file too big for one picture is split in parts: with `--out`, each part is written beside it as `name.2.diagram` and so on. Nothing in the file is run, and nothing is fetched. ## Options | Option | Default | What it does | |---|---|---| | `--out <file>` | standard output | Write the source to a file, and the parts of a split import beside it. | | `--title <text>` | a title from the file | The title of the diagram. | | `--format text\|json` | `text` | `json` prints the whole result: the source, the parts, the diagnostics, and the validation. | | `--view endpoints\|map\|sequence\|git\|timeline\|context\|container\|component` | the usual picture of the format | Which picture of a format that has several; for a Structurizr workspace, the C4 level. | | `--operation <id>` | Off | The OpenAPI operation a sequence view follows. | | `--chart auto\|xychart\|pie\|sankey\|treemap\|matrix` | `auto` | The chart a CSV or JSON table becomes. | | `--tables <names>` | every table | Only these tables of a schema, by name or prefix, separated by commas, such as `users,org_*`. | | `--tags <tags>` | every endpoint | Only the endpoints of an OpenAPI document with these tags, separated by commas. | | `--paths <prefixes>` | every endpoint | Only the endpoints under these paths, separated by commas, such as `/orders`. | | `--group-by module\|network\|file\|owner` | `module` | How infrastructure resources, or a Backstage catalog (`owner`), are framed. | | `--var-file <files>` | Off | Variables files to apply, separated by commas: `.tfvars`, `.bicepparam`, ARM parameters, or `Pulumi.<stack>.yaml`. | | `--root <folder>` | each root is drawn, as a part of its own | The root module, deployment, or program to draw when the folder holds several, by its folder or the end of it; for a Structurizr workspace, the system or container a view is about. | ## Examples Draw a database schema from its SQL. ```sh plotwork import-source sql schema.sql --out schema.diagram ``` Draw the endpoints of one tag of an API. ```sh plotwork import-source openapi openapi.yaml --tags orders ``` Draw a Terraform folder with the values of one environment. ```sh plotwork import-source terraform infra --var-file env/prod.tfvars --out infra.diagram ``` ## Exit codes | Code | When | |---|---| | `0` | The source was printed or written. | | `1` | Nothing could be imported from the file. | | `2` | A mistake in the command line: an unknown option, a missing operand, a file or glob that matches nothing, or a settings file that cannot be read. Also when the format or a value of `--view`, `--chart`, or `--group-by` is not one it knows. | ## See also | Doc | Why | |---|---| | [Command reference](https://docs.plotwork.dev/cli/commands.md) | Every command of the command line | | [Importing from code and data](https://docs.plotwork.dev/importing-from-code-and-data.md) | What each format becomes, and its limits | | [Exit codes and output formats](https://docs.plotwork.dev/cli/exit-codes-and-output.md) | What every code and format means | --- # plotwork import > Page: https://docs.plotwork.dev/cli/commands/import. Index of every page: https://docs.plotwork.dev/llms.txt Convert Mermaid to Plotwork: a `.mmd` file, or every `mermaid` fence of a Markdown file. It is a command of the Plotwork command line, the npm package `@buun_group/plotwork`. ## TL;DR | Question | Answer | |---|---| | What does it do? | Convert Mermaid to Plotwork: a `.mmd` file, or every `mermaid` fence of a Markdown file. | | How do I run it? | `npx -y @buun_group/plotwork@0.1.0-beta.13 import`, or `plotwork import` once the package is installed. | | What does it write? | The source with `--out`, or the Markdown file with `--write`. | | Does it use the network? | No. It reads and writes files on this machine only. | | Does it read the repository settings? | No. | | How do I read this in a terminal? | `plotwork import --help` | ## Synopsis ```sh plotwork import <file> [--out <file>] [--write] [--format text|json] [--lenient] ``` ## What it does Converts Mermaid to Plotwork. A `.mmd` or `.mermaid` file prints its Plotwork source. A Markdown file is reported on fence by fence, and rewritten only with `--write`; a fence that cannot be converted is left as it was. Each diagnostic says what was not carried over and how to say it in Plotwork, and the Plotwork validator then checks the result. ## Options | Option | Default | What it does | |---|---|---| | `--out <file>` | standard output | Write the source of a `.mmd` file to a file. | | `--write` | Off | Rewrite the Markdown file with each fence that converts. | | `--format text\|json` | `text` | `json` prints the conversion and its diagnostics as data. | | `--lenient` | Off | Convert what can be converted and report the rest, instead of refusing a diagram with a statement it cannot carry. | ## Examples Convert one Mermaid file. ```sh plotwork import flow.mmd --out flow.diagram ``` Convert every mermaid fence of a page that converts, in place. ```sh plotwork import README.md --write ``` ## Exit codes | Code | When | |---|---| | `0` | Everything converted. | | `1` | A diagram or a fence could not be converted, or the file could not be read. | | `2` | A mistake in the command line: an unknown option, a missing operand, a file or glob that matches nothing, or a settings file that cannot be read. Also when the file is not `.mmd`, `.mermaid`, or Markdown. | ## See also | Doc | Why | |---|---| | [Command reference](https://docs.plotwork.dev/cli/commands.md) | Every command of the command line | | [Migrating from Mermaid](https://docs.plotwork.dev/migrating-from-mermaid.md) | What converts and what does not | | [Exit codes and output formats](https://docs.plotwork.dev/cli/exit-codes-and-output.md) | What every code and format means | --- # plotwork export > Page: https://docs.plotwork.dev/cli/commands/export. Index of every page: https://docs.plotwork.dev/llms.txt Print paste-ready text of the first diagram for Confluence or Notion. It is a command of the Plotwork command line, the npm package `@buun_group/plotwork`. ## TL;DR | Question | Answer | |---|---| | What does it do? | Print paste-ready text of the first diagram for Confluence or Notion. | | How do I run it? | `npx -y @buun_group/plotwork@0.1.0-beta.13 export`, or `plotwork export` once the package is installed. | | What does it write? | The SVG beside the output when there is no `--image`; the text with `--out`. | | Does it use the network? | No. It reads and writes files on this machine only. | | Does it read the repository settings? | No. | | How do I read this in a terminal? | `plotwork export --help` | ## Synopsis ```sh plotwork export <file> --format confluence|confluence-wiki|notion [--image <https link>] [--out <file>] [--theme light|dark] [--no-source] ``` ## What it does Writes the first diagram of a file as text to paste into a wiki page. With `--image` the text shows the picture at that address, such as a hosted link; without it the SVG is written beside the output for you to upload, and the text names that file. The text goes to standard output or `--out`, and the steps around the paste go to standard error. ## Options | Option | Default | What it does | |---|---|---| | `--format confluence\|confluence-wiki\|notion` | Required | The wiki to write for. | | `--image <https link>` | the SVG is written beside the output | An https address the wiki can fetch the picture from. | | `--out <file>` | standard output | Write the text to a file. | | `--theme light\|dark` | `light` | The colours of the SVG written beside the output. | | `--no-source` | the source is included | Leave the diagram source out of the text. | ## Examples Text for a Notion page, with the SVG written beside it to upload. ```sh plotwork export checkout.diagram --format notion ``` Text for Confluence that shows a hosted picture. ```sh plotwork export flow.diagram --format confluence --image https://cdn.plotwork.dev/d/<token>.svg ``` ## Exit codes | Code | When | |---|---| | `0` | The text was printed or written. | | `1` | The diagram has an error. | | `2` | A mistake in the command line: an unknown option, a missing operand, a file or glob that matches nothing, or a settings file that cannot be read. Also when `--format` is missing or `--image` is not an https address. | ## See also | Doc | Why | |---|---| | [Command reference](https://docs.plotwork.dev/cli/commands.md) | Every command of the command line | | [Slack, email, Confluence, and Notion](https://docs.plotwork.dev/wikis-and-chat.md) | Pasting a diagram into a wiki | | [Exit codes and output formats](https://docs.plotwork.dev/cli/exit-codes-and-output.md) | What every code and format means | --- # plotwork publish > Page: https://docs.plotwork.dev/cli/commands/publish. Index of every page: https://docs.plotwork.dev/llms.txt Save every diagram of a `.diagram` or Markdown file to Plotwork, where your organisations' Publishing settings decide, and print the link. It is a command of the Plotwork command line, the npm package `@buun_group/plotwork`. ## TL;DR | Question | Answer | |---|---| | What does it do? | Save every diagram of a `.diagram` or Markdown file to Plotwork, where your organisations' Publishing settings decide, and print the link. | | How do I run it? | `npx -y @buun_group/plotwork@0.1.0-beta.13 publish`, or `plotwork publish` once the package is installed. | | What does it write? | Nothing on this machine. | | Does it use the network? | Yes: one request to the Plotwork API for each diagram, with the key in `PLOTWORK_API_KEY`, which is never printed. The source is sent, and the repository's header defaults and brand; nothing else of the file. | | Does it read the repository settings? | Yes: the header defaults of the settings file apply to every diagram. See [Configuration](https://docs.plotwork.dev/cli/configuration.md). | | How do I read this in a terminal? | `plotwork publish --help` | ## Synopsis ```sh plotwork publish <file> [--org <slug>] [--personal] [--folder <path>] [--name <name>] [--alias <name>] [--theme light|dark|auto] [--embed-source] [--config <file>] ``` ## What it does Checks every diagram of the file on this machine, then stores each one through the hosted API with the key in `PLOTWORK_API_KEY`. Where it goes is the API's decision: an organisation that keeps its members' diagrams in its private library gets it there, and the link printed is its page for members; otherwise it goes to your own library at a public link. A file with a mistake publishes nothing. A Markdown file publishes each `diagram` fence, named after the file and numbered. Publishing the same diagram again keeps its link. ## Options | Option | Default | What it does | |---|---|---| | `--org <slug>` | where your organisations' settings send it | Save it to this organisation's private library. | | `--personal` | Off | Ask for a public link in your own library; an organisation that requires its library refuses it. | | `--folder <path>` | the top level | The folder to file it in, such as `Payments/Checkout`. | | `--name <name>` | the file name | The name it is listed under. | | `--alias <name>` | Off | Point a stable link with this name at it, so one address shows the latest version. Personal only, and one diagram only. | | `--theme light\|dark\|auto` | the settings default, else `light` | The colour scheme to draw it in. | | `--embed-source` | Off | Write the source into the stored picture so it can be edited later; `--no-embed-source` leaves it out. | | `--config <file>` | `PLOTWORK_CONFIG`, else the nearest `plotwork.config.json` or `.plotwork/config.json` from the file up to the repository root | The repository settings file to read: header defaults, and the style rules check applies. | ## Examples Save it where your organisations send it, and print the link. ```sh plotwork publish checkout.diagram ``` Save every diagram of a Markdown file to Acme's library, in a folder. ```sh plotwork publish docs/architecture.md --org acme --folder Platform ``` Publish at a public link, behind a stable link. ```sh plotwork publish flow.diagram --personal --alias checkout-flow ``` ## Exit codes | Code | When | |---|---| | `0` | Every diagram was saved, and its link printed. | | `1` | A diagram has a mistake (nothing is sent), the API refused one (its code and words are printed), or the API could not be reached. | | `2` | A mistake in the command line: an unknown option, a missing operand, a file or glob that matches nothing, or a settings file that cannot be read. Also when `PLOTWORK_API_KEY` is missing or refused, `--personal` and `--org` are both given, or `--alias` is given for a file with several diagrams. | ## See also | Doc | Why | |---|---| | [Command reference](https://docs.plotwork.dev/cli/commands.md) | Every command of the command line | | [Where members publish](https://docs.plotwork.dev/organisations.md#13-where-members-publish) | Where a publish goes, and when it is private | | [Hosted diagrams](https://docs.plotwork.dev/hosted-diagrams.md) | Public links, stable links and folders | | [Exit codes and output formats](https://docs.plotwork.dev/cli/exit-codes-and-output.md) | What every code and format means | --- # plotwork icons > Page: https://docs.plotwork.dev/cli/commands/icons. Index of every page: https://docs.plotwork.dev/llms.txt Search the bundled icons by name. It is a command of the Plotwork command line, the npm package `@buun_group/plotwork`. ## TL;DR | Question | Answer | |---|---| | What does it do? | Search the bundled icons by name. | | How do I run it? | `npx -y @buun_group/plotwork@0.1.0-beta.13 icons`, or `plotwork icons` once the package is installed. | | What does it write? | Nothing. | | Does it use the network? | No. It reads and writes files on this machine only. | | Does it read the repository settings? | No. | | How do I read this in a terminal? | `plotwork icons --help` | ## Synopsis ```sh plotwork icons <query> ``` ## What it does Searches the icons bundled with the command line and prints up to 40 matches, one name per line, as it is written in `icon=`. The words of the query are joined, so `plotwork icons api gateway` needs no quotes. ## Options None. ## Examples Find the icon names for Postgres. ```sh plotwork icons postgres ``` ## Exit codes | Code | When | |---|---| | `0` | The search ran, whether or not anything matched. | | `1` | Not used. | | `2` | No query was given. | ## See also | Doc | Why | |---|---| | [Command reference](https://docs.plotwork.dev/cli/commands.md) | Every command of the command line | | [Icon names and lookup](https://docs.plotwork.dev/cloud-architecture/icon-names.md) | How icon names are formed | | [Icon reference](https://docs.plotwork.dev/reference/icons.md) | Every bundled icon | | [Exit codes and output formats](https://docs.plotwork.dev/cli/exit-codes-and-output.md) | What every code and format means | --- # API reference > Page: https://docs.plotwork.dev/api/reference. Index of every page: https://docs.plotwork.dev/llms.txt Check, render, and describe diagrams written in a small line-oriented language (flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, swimlane). Built for AI agents: every error carries a line, column, and fix hint, and the language reference is served as data. The same operations are available as MCP tools. ## TL;DR | Question | Answer | |---|---| | What format? | JSON in, JSON out. Every body is described by a schema, and each endpoint page links the ones it uses. | | Where is the live spec? | `GET /openapi.json` returns OpenAPI 3.1; `GET /docs` serves an interactive page. | | What does an error look like? | One envelope for every failure; see "Error envelope". A diagram with mistakes is a normal result from the validate route and a `422` from the render and outline routes. | | Where does it run? | The hosted API is at `https://api.plotwork.dev`, and every path below is relative to that address. Calling the drawing and checking routes needs an API key and the Pro plan. | | Which endpoints draw diagrams? | The ones under `/v1`: each serves a tool of the [tool reference](https://docs.plotwork.dev/mcp/tools.md), and its page names the tool. | ## Endpoints 130 endpoints, each with a page of its own. They are grouped by what they do. ### Diagrams | Method | Path | What it does | |---|---|---| | POST | [`/v1/diagrams/draw`](https://docs.plotwork.dev/api/post-v1-diagrams-draw.md) | Check, describe, and draw a diagram. | | POST | [`/v1/diagrams/edit`](https://docs.plotwork.dev/api/post-v1-diagrams-edit.md) | Change diagram source with small typed operations. | | POST | [`/v1/diagrams/convert-mermaid`](https://docs.plotwork.dev/api/post-v1-diagrams-convert-mermaid.md) | Convert one Mermaid diagram. | | POST | [`/v1/diagrams/validate`](https://docs.plotwork.dev/api/post-v1-diagrams-validate.md) | Check diagram source. | | POST | [`/v1/diagrams/render`](https://docs.plotwork.dev/api/post-v1-diagrams-render.md) | Render diagram source to SVG. | | POST | [`/v1/diagrams/outline`](https://docs.plotwork.dev/api/post-v1-diagrams-outline.md) | Describe diagram source as text. | | POST | [`/v1/diagrams/read`](https://docs.plotwork.dev/api/post-v1-diagrams-read.md) | Read the source back out of a Plotwork SVG. | | POST | [`/v1/diagrams/compare`](https://docs.plotwork.dev/api/post-v1-diagrams-compare.md) | Say what changed between two versions of a diagram. | | POST | [`/v1/diagrams/suggest`](https://docs.plotwork.dev/api/post-v1-diagrams-suggest.md) | Choose the kind of diagram for a request. | | POST | [`/v1/import`](https://docs.plotwork.dev/api/post-v1-import.md) | Turn a file of another format into a diagram. | ### Icons | Method | Path | What it does | |---|---|---| | POST | [`/v1/icons/map`](https://docs.plotwork.dev/api/post-v1-icons-map.md) | Choose icons for many names at once. | | GET | [`/v1/icons`](https://docs.plotwork.dev/api/get-v1-icons.md) | Find bundled icon names. | ### Markdown | Method | Path | What it does | |---|---|---| | POST | [`/v1/markdown/sync`](https://docs.plotwork.dev/api/post-v1-markdown-sync.md) | Turn a document's diagram fences into pictures. | | POST | [`/v1/markdown/plan`](https://docs.plotwork.dev/api/post-v1-markdown-plan.md) | Find the sections of a document a picture would help. | ### Patterns | Method | Path | What it does | |---|---|---| | POST | [`/v1/patterns/find`](https://docs.plotwork.dev/api/post-v1-patterns-find.md) | Find a ready-made architecture diagram. | | POST | [`/v1/patterns/get`](https://docs.plotwork.dev/api/post-v1-patterns-get.md) | Read a pattern filled for a flavour. | | POST | [`/v1/patterns/draw`](https://docs.plotwork.dev/api/post-v1-patterns-draw.md) | Fill a pattern and draw it. | ### Vocabulary | Method | Path | What it does | |---|---|---| | GET | [`/v1/vocabulary`](https://docs.plotwork.dev/api/get-v1-vocabulary.md) | Get the diagram language reference. | ### Auth | Method | Path | What it does | |---|---|---| | POST | [`/auth/code`](https://docs.plotwork.dev/api/post-auth-code.md) | Email a sign-in code. | | POST | [`/auth/verify`](https://docs.plotwork.dev/api/post-auth-verify.md) | Sign in with a code. | | POST | [`/auth/logout`](https://docs.plotwork.dev/api/post-auth-logout.md) | Sign out. | | POST | [`/auth/sso/discover`](https://docs.plotwork.dev/api/post-auth-sso-discover.md) | Ask whether an address signs in with single sign-on. | | POST | [`/auth/sso/start`](https://docs.plotwork.dev/api/post-auth-sso-start.md) | Start signing in through an organisation's identity provider. | | GET | [`/auth/sso/callback`](https://docs.plotwork.dev/api/get-auth-sso-callback.md) | Finish signing in through an identity provider. | | GET | [`/auth/sso/saml/{org}/metadata`](https://docs.plotwork.dev/api/get-auth-sso-saml-org-metadata.md) | Read an organisation's SAML service provider metadata. | | POST | [`/auth/sso/saml/{org}/acs`](https://docs.plotwork.dev/api/post-auth-sso-saml-org-acs.md) | Finish signing in through a SAML identity provider. | ### Keys | Method | Path | What it does | |---|---|---| | GET | [`/user/me`](https://docs.plotwork.dev/api/get-user-me.md) | Read the signed-in account. | | GET | [`/user/api-keys`](https://docs.plotwork.dev/api/get-user-api-keys.md) | List your API keys. | | POST | [`/user/api-keys`](https://docs.plotwork.dev/api/post-user-api-keys.md) | Create an API key. | | POST | [`/user/api-keys/{id}/reveal`](https://docs.plotwork.dev/api/post-user-api-keys-id-reveal.md) | Reveal an API key. | | DELETE | [`/user/api-keys/{id}`](https://docs.plotwork.dev/api/delete-user-api-keys-id.md) | Revoke an API key. | | GET | [`/user/usage`](https://docs.plotwork.dev/api/get-user-usage.md) | Read the account's usage. | ### Connections | Method | Path | What it does | |---|---|---| | POST | [`/user/mcp/approve`](https://docs.plotwork.dev/api/post-user-mcp-approve.md) | Approve a request to connect an agent. | | GET | [`/user/mcp/connections`](https://docs.plotwork.dev/api/get-user-mcp-connections.md) | List the agents you have connected. | | DELETE | [`/user/mcp/connections/{keyId}`](https://docs.plotwork.dev/api/delete-user-mcp-connections-key-id.md) | Disconnect an agent. | ### Hosting | Method | Path | What it does | |---|---|---| | POST | [`/v1/diagrams/publish`](https://docs.plotwork.dev/api/post-v1-diagrams-publish.md) | Publish a diagram and get a link. | | GET | [`/user/diagrams`](https://docs.plotwork.dev/api/get-user-diagrams.md) | List your hosted diagrams. | | PATCH | [`/user/diagrams/{id}`](https://docs.plotwork.dev/api/patch-user-diagrams-id.md) | File a hosted diagram in a folder. | | DELETE | [`/user/diagrams/{id}`](https://docs.plotwork.dev/api/delete-user-diagrams-id.md) | Delete a hosted diagram. | | PUT | [`/user/diagrams/{id}/lock`](https://docs.plotwork.dev/api/put-user-diagrams-id-lock.md) | Lock a hosted diagram. | | DELETE | [`/user/diagrams/{id}/lock`](https://docs.plotwork.dev/api/delete-user-diagrams-id-lock.md) | Unlock a hosted diagram. | | PUT | [`/user/diagrams/{id}/source`](https://docs.plotwork.dev/api/put-user-diagrams-id-source.md) | Save edited source over a hosted diagram. | | GET | [`/user/diagrams/{id}/versions`](https://docs.plotwork.dev/api/get-user-diagrams-id-versions.md) | List a hosted diagram's previous versions. | | POST | [`/user/diagrams/{id}/versions/{versionId}/restore`](https://docs.plotwork.dev/api/post-user-diagrams-id-versions-version-id-restore.md) | Restore a previous version of a hosted diagram. | | POST | [`/user/stable-links`](https://docs.plotwork.dev/api/post-user-stable-links.md) | Point a stable link at one of your diagrams. | | DELETE | [`/user/stable-links/{id}`](https://docs.plotwork.dev/api/delete-user-stable-links-id.md) | Delete a stable link. | | GET | [`/oembed`](https://docs.plotwork.dev/api/get-oembed.md) | Describe a diagram link for an embedder (oEmbed). | ### Organisations | Method | Path | What it does | |---|---|---| | GET | [`/orgs`](https://docs.plotwork.dev/api/get-orgs.md) | List your organisations. | | POST | [`/orgs`](https://docs.plotwork.dev/api/post-orgs.md) | Create an organisation. | | GET | [`/orgs/{org}`](https://docs.plotwork.dev/api/get-orgs-org.md) | Read an organisation. | | PATCH | [`/orgs/{org}`](https://docs.plotwork.dev/api/patch-orgs-org.md) | Rename an organisation. | | DELETE | [`/orgs/{org}`](https://docs.plotwork.dev/api/delete-orgs-org.md) | Delete an organisation. | | GET | [`/orgs/{org}/members`](https://docs.plotwork.dev/api/get-orgs-org-members.md) | List an organisation's members. | | PATCH | [`/orgs/{org}/members/{userId}`](https://docs.plotwork.dev/api/patch-orgs-org-members-user-id.md) | Change a member's role. | | DELETE | [`/orgs/{org}/members/{userId}`](https://docs.plotwork.dev/api/delete-orgs-org-members-user-id.md) | Remove a member, or leave. | | GET | [`/orgs/{org}/invites`](https://docs.plotwork.dev/api/get-orgs-org-invites.md) | List an organisation's pending invitations. | | POST | [`/orgs/{org}/invites`](https://docs.plotwork.dev/api/post-orgs-org-invites.md) | Invite an address to an organisation. | | DELETE | [`/orgs/{org}/invites/{id}`](https://docs.plotwork.dev/api/delete-orgs-org-invites-id.md) | Revoke an invitation. | | POST | [`/orgs/invites/accept`](https://docs.plotwork.dev/api/post-orgs-invites-accept.md) | Accept an invitation. | | GET | [`/orgs/{org}/diagrams`](https://docs.plotwork.dev/api/get-orgs-org-diagrams.md) | List an organisation's diagrams. | | POST | [`/orgs/{org}/diagrams`](https://docs.plotwork.dev/api/post-orgs-org-diagrams.md) | Publish a diagram to an organisation. | | GET | [`/orgs/{org}/diagrams/{id}/svg`](https://docs.plotwork.dev/api/get-orgs-org-diagrams-id-svg.md) | Download an organisation diagram's SVG. | | PATCH | [`/orgs/{org}/diagrams/{id}`](https://docs.plotwork.dev/api/patch-orgs-org-diagrams-id.md) | File or rename an organisation diagram. | | DELETE | [`/orgs/{org}/diagrams/{id}`](https://docs.plotwork.dev/api/delete-orgs-org-diagrams-id.md) | Delete an organisation diagram. | | PUT | [`/orgs/{org}/diagrams/{id}/lock`](https://docs.plotwork.dev/api/put-orgs-org-diagrams-id-lock.md) | Lock an organisation diagram. | | DELETE | [`/orgs/{org}/diagrams/{id}/lock`](https://docs.plotwork.dev/api/delete-orgs-org-diagrams-id-lock.md) | Unlock an organisation diagram. | | PUT | [`/orgs/{org}/diagrams/{id}/source`](https://docs.plotwork.dev/api/put-orgs-org-diagrams-id-source.md) | Save edited source over an organisation diagram. | | GET | [`/orgs/{org}/diagrams/{id}/versions`](https://docs.plotwork.dev/api/get-orgs-org-diagrams-id-versions.md) | List an organisation diagram's previous versions. | | POST | [`/orgs/{org}/diagrams/{id}/versions/{versionId}/restore`](https://docs.plotwork.dev/api/post-orgs-org-diagrams-id-versions-version-id-restore.md) | Restore a previous version of an organisation diagram. | | PUT | [`/orgs/{org}/editable`](https://docs.plotwork.dev/api/put-orgs-org-editable.md) | Keep an organisation's diagrams editable. | | PUT | [`/orgs/{org}/publishing`](https://docs.plotwork.dev/api/put-orgs-org-publishing.md) | Choose where an organisation's members publish. | | GET | [`/orgs/{org}/publishing/public-diagrams`](https://docs.plotwork.dev/api/get-orgs-org-publishing-public-diagrams.md) | List members' public diagrams for review. | | POST | [`/orgs/{org}/publishing/public-diagrams/{id}/move`](https://docs.plotwork.dev/api/post-orgs-org-publishing-public-diagrams-id-move.md) | Move a member's public diagram into the library. | | DELETE | [`/orgs/{org}/publishing/public-diagrams/{id}`](https://docs.plotwork.dev/api/delete-orgs-org-publishing-public-diagrams-id.md) | Unpublish a member's public diagram. | | GET | [`/user/publishing`](https://docs.plotwork.dev/api/get-user-publishing.md) | Read where your publishes go. | | PUT | [`/user/publishing`](https://docs.plotwork.dev/api/put-user-publishing.md) | Choose your default organisation for publishing. | | GET | [`/orgs/{org}/folder-rules`](https://docs.plotwork.dev/api/get-orgs-org-folder-rules.md) | List an organisation's folder rules. | | PUT | [`/orgs/{org}/folder-rules`](https://docs.plotwork.dev/api/put-orgs-org-folder-rules.md) | Restrict a folder. | | DELETE | [`/orgs/{org}/folder-rules`](https://docs.plotwork.dev/api/delete-orgs-org-folder-rules.md) | Remove a folder's rule. | | GET | [`/orgs/{org}/keys`](https://docs.plotwork.dev/api/get-orgs-org-keys.md) | List an organisation's API keys. | | POST | [`/orgs/{org}/keys`](https://docs.plotwork.dev/api/post-orgs-org-keys.md) | Create an organisation API key. | | POST | [`/orgs/{org}/keys/{id}/reveal`](https://docs.plotwork.dev/api/post-orgs-org-keys-id-reveal.md) | Reveal an organisation API key. | | DELETE | [`/orgs/{org}/keys/{id}`](https://docs.plotwork.dev/api/delete-orgs-org-keys-id.md) | Revoke an organisation API key. | | GET | [`/orgs/{org}/brand`](https://docs.plotwork.dev/api/get-orgs-org-brand.md) | Read an organisation's brand theme. | | PUT | [`/orgs/{org}/brand`](https://docs.plotwork.dev/api/put-orgs-org-brand.md) | Save an organisation's brand theme. | | DELETE | [`/orgs/{org}/brand`](https://docs.plotwork.dev/api/delete-orgs-org-brand.md) | Remove an organisation's brand theme. | | POST | [`/orgs/{org}/brand/preview`](https://docs.plotwork.dev/api/post-orgs-org-brand-preview.md) | Preview brand colors. | | GET | [`/user/brand`](https://docs.plotwork.dev/api/get-user-brand.md) | Read the brand of your own calls. | | PUT | [`/user/brand`](https://docs.plotwork.dev/api/put-user-brand.md) | Choose the brand of your own calls. | | GET | [`/orgs/{org}/style-rules`](https://docs.plotwork.dev/api/get-orgs-org-style-rules.md) | Read an organisation's style rules. | | PUT | [`/orgs/{org}/style-rules`](https://docs.plotwork.dev/api/put-orgs-org-style-rules.md) | Save an organisation's style rules. | | DELETE | [`/orgs/{org}/style-rules`](https://docs.plotwork.dev/api/delete-orgs-org-style-rules.md) | Remove an organisation's style rules. | | GET | [`/orgs/{org}/icons`](https://docs.plotwork.dev/api/get-orgs-org-icons.md) | List an organisation's custom icons. | | PUT | [`/orgs/{org}/icons/{name}`](https://docs.plotwork.dev/api/put-orgs-org-icons-name.md) | Upload or replace a custom icon. | | DELETE | [`/orgs/{org}/icons/{name}`](https://docs.plotwork.dev/api/delete-orgs-org-icons-name.md) | Delete a custom icon. | | GET | [`/orgs/{org}/icons/{name}/preview`](https://docs.plotwork.dev/api/get-orgs-org-icons-name-preview.md) | Preview a custom icon in a diagram. | | GET | [`/orgs/{org}/avatar`](https://docs.plotwork.dev/api/get-orgs-org-avatar.md) | Read an organisation's avatar. | | PUT | [`/orgs/{org}/avatar`](https://docs.plotwork.dev/api/put-orgs-org-avatar.md) | Upload or replace an organisation's avatar. | | DELETE | [`/orgs/{org}/avatar`](https://docs.plotwork.dev/api/delete-orgs-org-avatar.md) | Remove an organisation's avatar. | | POST | [`/orgs/{org}/drift/runs`](https://docs.plotwork.dev/api/post-orgs-org-drift-runs.md) | Report a drift run from CI. | | GET | [`/orgs/{org}/drift`](https://docs.plotwork.dev/api/get-orgs-org-drift.md) | Read an organisation's drift dashboard. | | GET | [`/orgs/{org}/audit`](https://docs.plotwork.dev/api/get-orgs-org-audit.md) | Read an organisation's audit log. | | GET | [`/orgs/{org}/audit/export`](https://docs.plotwork.dev/api/get-orgs-org-audit-export.md) | Export an organisation's audit log. | | GET | [`/orgs/{org}/sso`](https://docs.plotwork.dev/api/get-orgs-org-sso.md) | Read an organisation's single sign-on settings. | | PUT | [`/orgs/{org}/sso/connection`](https://docs.plotwork.dev/api/put-orgs-org-sso-connection.md) | Set up an OpenID Connect identity provider. | | PATCH | [`/orgs/{org}/sso/connection`](https://docs.plotwork.dev/api/patch-orgs-org-sso-connection.md) | Turn single sign-on on or off, and set Require SSO and who joins on first sign-in. | | DELETE | [`/orgs/{org}/sso/connection`](https://docs.plotwork.dev/api/delete-orgs-org-sso-connection.md) | Remove the identity provider. | | PUT | [`/orgs/{org}/sso/saml`](https://docs.plotwork.dev/api/put-orgs-org-sso-saml.md) | Set up a SAML identity provider. | | POST | [`/orgs/{org}/sso/domains`](https://docs.plotwork.dev/api/post-orgs-org-sso-domains.md) | Add an email domain. | | POST | [`/orgs/{org}/sso/domains/{domain}/verify`](https://docs.plotwork.dev/api/post-orgs-org-sso-domains-domain-verify.md) | Check a domain's DNS record. | | DELETE | [`/orgs/{org}/sso/domains/{domain}`](https://docs.plotwork.dev/api/delete-orgs-org-sso-domains-domain.md) | Remove an email domain. | | GET | [`/orgs/{org}/scim`](https://docs.plotwork.dev/api/get-orgs-org-scim.md) | Read an organisation's SCIM settings. | | POST | [`/orgs/{org}/scim/token`](https://docs.plotwork.dev/api/post-orgs-org-scim-token.md) | Make or rotate the SCIM token. | | DELETE | [`/orgs/{org}/scim/token`](https://docs.plotwork.dev/api/delete-orgs-org-scim-token.md) | Revoke the SCIM token. | | PATCH | [`/orgs/{org}/scim/groups/{group}`](https://docs.plotwork.dev/api/patch-orgs-org-scim-groups-group.md) | Choose the role a SCIM group gives. | | GET | [`/orgs/{org}/support`](https://docs.plotwork.dev/api/get-orgs-org-support.md) | Read what priority support is for an organisation. | | POST | [`/orgs/{org}/support/requests`](https://docs.plotwork.dev/api/post-orgs-org-support-requests.md) | Ask the organisation's named contact for help. | ### Billing | Method | Path | What it does | |---|---|---| | GET | [`/user/billing`](https://docs.plotwork.dev/api/get-user-billing.md) | Read the account's plan. | | POST | [`/user/billing/checkout`](https://docs.plotwork.dev/api/post-user-billing-checkout.md) | Start a checkout for a paid plan. | | POST | [`/user/billing/verify-checkout`](https://docs.plotwork.dev/api/post-user-billing-verify-checkout.md) | Read a finished checkout back. | | POST | [`/user/billing/cancel`](https://docs.plotwork.dev/api/post-user-billing-cancel.md) | Cancel the plan at the end of the period. | | POST | [`/user/billing/resume`](https://docs.plotwork.dev/api/post-user-billing-resume.md) | Take back a cancellation. | | POST | [`/user/billing/portal`](https://docs.plotwork.dev/api/post-user-billing-portal.md) | Open the Stripe billing portal. | | GET | [`/user/billing/documents`](https://docs.plotwork.dev/api/get-user-billing-documents.md) | List your invoices and receipts. | | GET | [`/user/billing/documents/{id}`](https://docs.plotwork.dev/api/get-user-billing-documents-id.md) | Read one invoice or receipt. | | GET | [`/user/billing/documents/{id}/pdf`](https://docs.plotwork.dev/api/get-user-billing-documents-id-pdf.md) | Download an invoice or receipt as a PDF. | | POST | [`/billing/webhook`](https://docs.plotwork.dev/api/post-billing-webhook.md) | Receive a Stripe event. Called by Stripe, not by clients. | ### Enterprise | Method | Path | What it does | |---|---|---| | POST | [`/enterprise/enquiries`](https://docs.plotwork.dev/api/post-enterprise-enquiries.md) | Send an Enterprise enquiry. | ### Telemetry | Method | Path | What it does | |---|---|---| | POST | [`/telemetry/usage`](https://docs.plotwork.dev/api/post-telemetry-usage.md) | Receive the opt-in daily totals of the local npm package. | ### Health | Method | Path | What it does | |---|---|---| | GET | [`/health`](https://docs.plotwork.dev/api/get-health.md) | Check the service is up. | ## Error envelope Every failure uses the same shape, so a client needs one error handler. | Field | Type | Required | Description | |---|---|---|---| | `error` | `true` | yes | Always true, so clients can branch without reading the status. | | `errorCode` | `string` | yes | Stable snake_case key such as validation.failed or diagram.invalid. | | `message` | `string` | yes | Safe, human-readable summary. | | `details` | `any` | no | Validation issues or diagnostics, JSON-safe. | | `requestId` | `string` | no | Trace id for correlating with logs. | ## See also | Doc | Why | |---|---| | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The same operations as callable tools | | [Language reference](https://docs.plotwork.dev/reference/language.md) | What the diagram source may contain | | [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md) | The language by example | --- # Schemas > Page: https://docs.plotwork.dev/api/schemas. Index of every page: https://docs.plotwork.dev/llms.txt Every request and response type of the HTTP API, with its fields. Each endpoint page links the types it uses. ## TL;DR | Question | Answer | |---|---| | What is a schema? | The shape of a JSON body: its fields, their types, and which are required. | | Where do I find the one an endpoint uses? | On the endpoint page, whose request and response types link here. | | Where is the machine-readable form? | `GET /openapi.json` returns every schema as OpenAPI 3.1 JSON Schema. | | How many are there? | 180. | ## HealthResponse Liveness answer. | Field | Type | Required | Description | |---|---|---|---| | `status` | `"ok"` | yes | | | `version` | `string` | yes | Package version of the running service. | | `kinds` | `string[]` | yes | Diagram kinds this build can render. | ## Accepted | Field | Type | Required | Description | |---|---|---|---| | `accepted` | `true` | yes | | ## ErrorResponse Standard error envelope used by every failing response. | Field | Type | Required | Description | |---|---|---|---| | `error` | `true` | yes | Always true, so clients can branch without reading the status. | | `errorCode` | `string` | yes | Stable snake_case key such as validation.failed or diagram.invalid. | | `message` | `string` | yes | Safe, human-readable summary. | | `details` | `any` | no | Validation issues or diagnostics, JSON-safe. | | `requestId` | `string` | no | Trace id for correlating with logs. | ## RequestCodeBody | Field | Type | Required | Description | |---|---|---|---| | `email` | `string` | yes | at least 3 characters; at most 254 characters. | ## SignInResponse | Field | Type | Required | Description | |---|---|---|---| | `user` | `Account` | yes | | | `user.id` | `string` | yes | | | `user.email` | `string` | yes | | ## Account | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `email` | `string` | yes | | ## VerifyCodeBody | Field | Type | Required | Description | |---|---|---|---| | `email` | `string` | yes | at least 3 characters; at most 254 characters. | | `code` | `string` | yes | pattern `^\d{6}$`. | ## SsoDiscover | Field | Type | Required | Description | |---|---|---|---| | `sso` | `boolean` | yes | True when the address's domain signs in through an organisation's identity provider. Names no organisation. | ## SsoDiscoverBody | Field | Type | Required | Description | |---|---|---|---| | `email` | `string` | yes | at least 3 characters; at most 254 characters. | ## SsoStart | Field | Type | Required | Description | |---|---|---|---| | `url` | `string` | yes | The identity provider's authorization address to send the browser to. | ## SsoStartBody | Field | Type | Required | Description | |---|---|---|---| | `email` | `string` | yes | at least 3 characters; at most 254 characters. | | `returnTo` | `string` | no | Where to send the browser after signing in. Kept only when its origin is one of Plotwork's; otherwise the portal. at most 2000 characters. | ## ApiKeyList | Field | Type | Required | Description | |---|---|---|---| | `items` | `ApiKeySummary[]` | yes | | | `items[].id` | `string` | yes | | | `items[].name` | `string` | yes | | | `items[].prefix` | `string` | yes | | | `items[].last4` | `string` | yes | | | `items[].createdAt` | `integer` | yes | Milliseconds since the epoch. | | `items[].lastUsedAt` | `integer \| null` | yes | Milliseconds since the epoch, or null if never used. | | `items[].revealable` | `boolean` | yes | True when the full key can be shown again with the reveal route. False for a key made before that was possible, or on a server that cannot seal keys. | ## ApiKeySummary | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `name` | `string` | yes | | | `prefix` | `string` | yes | | | `last4` | `string` | yes | | | `createdAt` | `integer` | yes | Milliseconds since the epoch. | | `lastUsedAt` | `integer \| null` | yes | Milliseconds since the epoch, or null if never used. | | `revealable` | `boolean` | yes | True when the full key can be shown again with the reveal route. False for a key made before that was possible, or on a server that cannot seal keys. | ## CreatedApiKey | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `name` | `string` | yes | | | `prefix` | `string` | yes | | | `last4` | `string` | yes | | | `createdAt` | `integer` | yes | Milliseconds since the epoch. | | `lastUsedAt` | `integer \| null` | yes | Milliseconds since the epoch, or null if never used. | | `revealable` | `boolean` | yes | True when the full key can be shown again with the reveal route. False for a key made before that was possible, or on a server that cannot seal keys. | | `key` | `string` | yes | The full key. When `revealable` is false this is the only time it is shown, so store it now. | ## CreateApiKeyBody | Field | Type | Required | Description | |---|---|---|---| | `name` | `string` | yes | at least 1 character; at most 60 characters; pattern `\S`. | ## RevealedApiKey | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `key` | `string` | yes | The full key. | ## Approval | Field | Type | Required | Description | |---|---|---|---| | `approval` | `string` | yes | The signed approval. It carries the connection key sealed, so it is safe in an address. | | `expiresIn` | `integer` | yes | Seconds the approval stays usable. | | `redirectTo` | `string` | yes | Where to send the browser: the MCP server callback with the request and the approval already attached. | ## ApproveConnectionBody | Field | Type | Required | Description | |---|---|---|---| | `request` | `string` | yes | The signed authorization request, exactly as the MCP server put it in the address. at least 1 character; at most 6144 characters. | ## ConnectionList | Field | Type | Required | Description | |---|---|---|---| | `items` | `Connection[]` | yes | | | `items[].id` | `string` | yes | | | `items[].clientId` | `string` | yes | The OAuth client id the agent registered with. | | `items[].clientName` | `string` | yes | The name the agent gave itself when it registered. It is a claim, not a fact. | | `items[].connectedAt` | `integer` | yes | Milliseconds since the epoch. | | `items[].lastUsedAt` | `integer \| null` | yes | Milliseconds since the epoch, or null if the agent has not made a call. | ## Connection | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `clientId` | `string` | yes | The OAuth client id the agent registered with. | | `clientName` | `string` | yes | The name the agent gave itself when it registered. It is a claim, not a fact. | | `connectedAt` | `integer` | yes | Milliseconds since the epoch. | | `lastUsedAt` | `integer \| null` | yes | Milliseconds since the epoch, or null if the agent has not made a call. | ## Usage | Field | Type | Required | Description | |---|---|---|---| | `plan` | `"free" \| "pro"` | yes | The plan the account is on, which decides the limit. | | `limit` | `integer` | yes | Metered calls the account may make per UTC day, across every key and connected agent. | | `used` | `integer` | yes | Metered calls made today. minimum 0. | | `remaining` | `integer` | yes | Calls left today. Zero when the limit is used up. minimum 0. | | `resetsAt` | `integer` | yes | When the count goes back to zero (00:00 UTC), in milliseconds since the epoch. | | `today` | `object` | yes | Today's calls, split by how they reached the service. | | `today.api` | `integer` | yes | minimum 0. | | `today.mcp` | `integer` | yes | minimum 0. | | `days` | `UsageDay[]` | yes | The last 30 days, oldest first, ending today. A day with no calls is present with zeros. | | `days[].day` | `string` | yes | The UTC date, as YYYY-MM-DD. | | `days[].api` | `integer` | yes | REST API calls that day. minimum 0. | | `days[].mcp` | `integer` | yes | MCP tool calls that day. minimum 0. | ## UsageDay | Field | Type | Required | Description | |---|---|---|---| | `day` | `string` | yes | The UTC date, as YYYY-MM-DD. | | `api` | `integer` | yes | REST API calls that day. minimum 0. | | `mcp` | `integer` | yes | MCP tool calls that day. minimum 0. | ## BillingState | Field | Type | Required | Description | |---|---|---|---| | `plan` | `"free" \| "pro"` | yes | The plan the account gets right now. It is the free plan whenever the subscription is not paying, whatever plan was bought. | | `status` | `string` | yes | Stripe's status for the subscription (`active`, `trialing`, `past_due`, `canceled`, `unpaid`, `incomplete`, and so on), or `none` when the account never started one. | | `currentPeriodEnd` | `integer \| null` | yes | When the period paid for ends, in milliseconds since the epoch, or null when there is no subscription. | | `cancelAtPeriodEnd` | `boolean` | yes | True while the subscription is scheduled to end at the close of the period already paid for. | | `hasCustomer` | `boolean` | yes | True once the account has billing details at Stripe, which is when the billing portal can be opened. | ## BillingUrl | Field | Type | Required | Description | |---|---|---|---| | `url` | `string` | yes | An address on Stripe's own site. Send the browser there; it expires, so do not store it. | ## CheckoutBody | Field | Type | Required | Description | |---|---|---|---| | `plan` | `"pro"` | yes | The plan to subscribe to. `pro` is the only paid plan. | ## VerifyCheckoutBody | Field | Type | Required | Description | |---|---|---|---| | `sessionId` | `string` | yes | The Checkout Session id Stripe put in the return address. pattern `^cs_[A-Za-z0-9_]{8,200}$`. | ## CancelBody | Field | Type | Required | Description | |---|---|---|---| | `reason` | `string` | no | Why the person is cancelling, in their own words. Optional. Sent to Stripe as the cancellation comment; Plotwork does not store it. at most 500 characters. | ## BillingDocumentList | Field | Type | Required | Description | |---|---|---|---| | `items` | `BillingDocument[]` | yes | | | `items[].id` | `string` | yes | The id to ask for the document and its PDF with. | | `items[].kind` | `"invoice" \| "receipt"` | yes | A `receipt` is an invoice that has been paid; anything else is an `invoice`. | | `items[].number` | `string` | yes | The number printed on the document, which an accounts department quotes back. | | `items[].description` | `string` | yes | What it was for, such as the plan. Empty when Stripe names nothing. | | `items[].currency` | `string` | yes | The three-letter currency, in capitals. at least 3 characters; at most 3 characters. | | `items[].totalCents` | `integer` | yes | The total in whole cents (the currency's minor unit). | | `items[].status` | `string` | yes | The invoice's own word: `paid`, `open`, `void`, or `uncollectible`. A draft is never listed. | | `items[].issuedAt` | `integer` | yes | When it was issued, in milliseconds since the epoch. | | `items[].paidAt` | `integer \| null` | yes | When it was paid, in milliseconds since the epoch, or null while it is not. | | `items[].downloadable` | `boolean` | yes | False when there is no PDF to fetch, so the PDF route would answer 404. | ## BillingDocument | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The id to ask for the document and its PDF with. | | `kind` | `"invoice" \| "receipt"` | yes | A `receipt` is an invoice that has been paid; anything else is an `invoice`. | | `number` | `string` | yes | The number printed on the document, which an accounts department quotes back. | | `description` | `string` | yes | What it was for, such as the plan. Empty when Stripe names nothing. | | `currency` | `string` | yes | The three-letter currency, in capitals. at least 3 characters; at most 3 characters. | | `totalCents` | `integer` | yes | The total in whole cents (the currency's minor unit). | | `status` | `string` | yes | The invoice's own word: `paid`, `open`, `void`, or `uncollectible`. A draft is never listed. | | `issuedAt` | `integer` | yes | When it was issued, in milliseconds since the epoch. | | `paidAt` | `integer \| null` | yes | When it was paid, in milliseconds since the epoch, or null while it is not. | | `downloadable` | `boolean` | yes | False when there is no PDF to fetch, so the PDF route would answer 404. | ## BillingDocumentDetail | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The id to ask for the document and its PDF with. | | `kind` | `"invoice" \| "receipt"` | yes | A `receipt` is an invoice that has been paid; anything else is an `invoice`. | | `number` | `string` | yes | The number printed on the document, which an accounts department quotes back. | | `description` | `string` | yes | What it was for, such as the plan. Empty when Stripe names nothing. | | `currency` | `string` | yes | The three-letter currency, in capitals. at least 3 characters; at most 3 characters. | | `totalCents` | `integer` | yes | The total in whole cents (the currency's minor unit). | | `status` | `string` | yes | The invoice's own word: `paid`, `open`, `void`, or `uncollectible`. A draft is never listed. | | `issuedAt` | `integer` | yes | When it was issued, in milliseconds since the epoch. | | `paidAt` | `integer \| null` | yes | When it was paid, in milliseconds since the epoch, or null while it is not. | | `downloadable` | `boolean` | yes | False when there is no PDF to fetch, so the PDF route would answer 404. | | `lines` | `DocumentLine[]` | yes | | | `lines[].description` | `string` | yes | | | `lines[].quantity` | `integer` | yes | minimum 1. | | `lines[].amountCents` | `integer` | yes | | | `lines[].periodStart` | `integer \| null` | yes | The start of the service period, in milliseconds since the epoch, or null. | | `lines[].periodEnd` | `integer \| null` | yes | The end of the service period, in milliseconds since the epoch, or null. | | `subtotalCents` | `integer \| null` | yes | | | `taxCents` | `integer \| null` | yes | The tax, or null when Stripe names none. | | `amountPaidCents` | `integer \| null` | yes | | | `billedTo` | `object` | yes | | | `billedTo.name` | `string \| null` | yes | | | `billedTo.email` | `string \| null` | yes | | | `issuer` | `object \| null` | yes | The Stripe account's business name and its tax numbers, from the invoice. There is no address: Stripe does not put one on an invoice. | | `issuer.name` | `string` | yes | | | `issuer.lines` | `string[]` | yes | | | `paidWith` | `object \| null` | yes | The card the payment was taken from: its brand and last four digits, and nothing more of it. Null when it was not paid by card. | | `paidWith.brand` | `string` | yes | | | `paidWith.last4` | `string` | yes | pattern `^\d{4}$`. | ## DocumentLine | Field | Type | Required | Description | |---|---|---|---| | `description` | `string` | yes | | | `quantity` | `integer` | yes | minimum 1. | | `amountCents` | `integer` | yes | | | `periodStart` | `integer \| null` | yes | The start of the service period, in milliseconds since the epoch, or null. | | `periodEnd` | `integer \| null` | yes | The end of the service period, in milliseconds since the epoch, or null. | ## WebhookAck | Field | Type | Required | Description | |---|---|---|---| | `received` | `true` | yes | | | `outcome` | `"handled" \| "duplicate" \| "ignored"` | yes | `handled` when the event changed or confirmed an account, `duplicate` when it had been handled before, `ignored` when it is not about a Plotwork subscription. | ## DrawDiagramResponse The result of checking, describing, and drawing a diagram in one call. | Field | Type | Required | Description | |---|---|---|---| | `valid` | `boolean` | yes | True when the source has no errors. When false, nothing was drawn: fix the errors and call again. | | `kind` | `string \| null` | yes | The diagram kind, or null when the header could not be read. | | `errors` | `Diagnostic[]` | yes | Every error found in one pass, each with line, column, and a hint. Empty when valid. | | `errors[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `errors[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `errors[].message` | `string` | yes | What is wrong, in one sentence. | | `errors[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `errors[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `errors[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `errors[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `warnings` | `Diagnostic[]` | yes | Quality warnings for a valid diagram: too dense, too wide, text cut, unconnected nodes, and suggestions for an icon or a group type. Empty when invalid. | | `warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `summary` | `string \| null` | yes | One-line description of the diagram, usable as alt text; null when invalid. | | `outline` | `string[]` | yes | The diagram as plain text in reading order, to check it says what you meant; empty when invalid. | | `outlineTruncated` | `boolean` | yes | True when the summary level cut the outline short. Call again with detail "full" to read all of it. | | `size` | `object \| null` | yes | Natural size of the laid-out diagram; null when invalid. | | `size.width` | `integer` | yes | Width in px. minimum 1. | | `size.height` | `integer` | yes | Height in px. minimum 1. | | `quality` | `object` | no | How clean the picture looks (score, grade, measures) and up to three changes that would help; read it instead of looking. Absent when invalid. | | `quality.score` | `integer` | yes | 0 to 100; 100 is a picture with nothing to fix. The same diagram always gets the same score. minimum 0; maximum 100. | | `quality.grade` | `"clean" \| "fair" \| "cluttered"` | yes | clean is 85 or more, fair 60 or more, cluttered below. | | `quality.measures` | `object` | yes | What the score is made from: crossings, lineOverBox, labelOverlaps, smallestTextPx (in an 880px column), aspectRatio, emptySpace (0 to 1), longestLine (px), detour (1 is straight), the counts of boxes, lines, labels, series, and slices, and any measure the kind adds. | | `quality.hints` | `string[]` | yes | Up to three changes, the one worth most first, each naming the statement or option to edit. | | `quality.line` | `string` | yes | The grade, the score, and the first hint in one line. | | `notices` | `Note[]` | no | Information notes, such as an icon alias drawn as the real icon; absent when there is none. | | `notices[].code` | `string` | yes | A stable code, such as I122 for an icon alias. | | `notices[].severity` | `"info"` | yes | A note never stops a drawing and is not a warning. | | `notices[].message` | `string` | yes | | | `notices[].line` | `integer` | yes | minimum 1. | | `notices[].column` | `integer` | yes | minimum 1. | | `notices[].length` | `integer` | yes | minimum 1. | | `notices[].hint` | `string \| null` | yes | The text to write instead. | | `renders` | `object[]` | yes | One picture per requested color scheme, light first; empty when invalid, when svg is never, and when every picture was left out (see picturesOmitted). | | `renders[].theme` | `"light" \| "dark"` | yes | The color scheme this picture is drawn in. | | `renders[].svg` | `string` | yes | Self-contained SVG markup with an accessible title and description. | | `renders[].size` | `Size` | yes | Natural size of a rendered diagram in px. | | `renders[].size.width` | `integer` | yes | Width in px. minimum 1. | | `renders[].size.height` | `integer` | yes | Height in px. minimum 1. | | `picturesOmitted` | `object` | no | Present only when pictures were left out because the answer would have been too big for an AI client to read in one piece. | | `picturesOmitted.themes` | `("light" \| "dark")[]` | yes | The color schemes whose pictures were left out. | | `picturesOmitted.characters` | `integer` | yes | How many characters the pictures left out came to. | | `picturesOmitted.limit` | `integer` | yes | The most characters one answer may hold before the pictures are left out. | | `picturesOmitted.why` | `string` | yes | Why they were left out, and what to do. | | `viewUrl` | `string` | no | A link that shows a valid diagram in a browser; give it to the person who wants to look. The source is in the part after #, never sent to a server, but anyone with the link can read it. Absent when too long; next says so. | | `pngUrl` | `string` | no | The viewUrl with &download=png: opening it in a browser saves the diagram as a PNG for Slack, email, or a wiki. The PNG is drawn in the person's browser, not on a server, so it is a link to open, not an image address to embed. Absent when too long. | | `svgUrl` | `string` | no | The viewUrl with &download=svg: a person opens it to download the SVG. Not a file address for curl; use saveCommand. Absent when too long. | | `saveCommand` | `string` | no | Run in a shell to save the SVG into the repository: the CLI decodes the link locally, fetches nothing, and embeds the source. Set --out to the path; add --theme dark for dark. Absent when too long. | | `next` | `string` | yes | The next step in one sentence. | ## Diagnostic A located, actionable problem found in diagram source. | Field | Type | Required | Description | |---|---|---|---| | `code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `message` | `string` | yes | What is wrong, in one sentence. | | `line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `column` | `integer` | yes | 1-based column in that line. minimum 1. | | `length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | ## Note An information note with its place in the source. | Field | Type | Required | Description | |---|---|---|---| | `code` | `string` | yes | A stable code, such as I122 for an icon alias. | | `severity` | `"info"` | yes | A note never stops a drawing and is not a warning. | | `message` | `string` | yes | | | `line` | `integer` | yes | minimum 1. | | `column` | `integer` | yes | minimum 1. | | `length` | `integer` | yes | minimum 1. | | `hint` | `string \| null` | yes | The text to write instead. | ## Size Natural size of a rendered diagram in px. | Field | Type | Required | Description | |---|---|---|---| | `width` | `integer` | yes | Width in px. minimum 1. | | `height` | `integer` | yes | Height in px. minimum 1. | ## DrawDiagramInput Diagram source and what to return: the color schemes to draw and how much detail. | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | Diagram source. The first line is the header: flow "Title", sequence "Title", schema "Title", git "Title", gantt "Title", class "Title", state "Title", timeline "Title", journey "Title", mindmap "Title", treemap "Title", pie "Title", quadrant "Title", layers "Title", c4 "Title", matrix "Title", sankey "Title", xychart "Title", tree "Title", or swimlane "Title". at least 1 character; at most 50000 characters. | | `themes` | `"light" \| "dark" \| "both"` | no | light, dark, or both (the default): the light picture and the dark one a dark page needs. default "both". | | `detail` | `"summary" \| "full"` | no | summary (the default), or full: the whole outline, and the source embedded in each SVG. default "summary". | | `svg` | `"auto" \| "always" \| "never"` | no | auto (the default) returns the SVGs, but over MCP leaves them out when the answer would be too big for a client (picturesOmitted). always returns them at any size. never draws nothing, which is cheaper. The HTTP API treats auto as always. default "auto". | | `embedSource` | `boolean` | no | Write the source into each SVG so read_diagram can give it back. Set it when you save the SVG in a repository or publish it to edit later; anyone with the file or link can then read the source. default false. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## EditDiagramResponse The edited source, a result for each operation, and the check of the result. | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | The edited source. Lines no operation touched are byte for byte as they were, comments included. | | `changed` | `boolean` | yes | | | `results` | `object[]` | yes | One entry for each operation, in order. | | `results[].op` | `"rename" \| "set" \| "add_node" \| "remove" \| "add_edge" \| "remove_edge" \| "move" \| "replace_text"` | yes | | | `results[].applied` | `boolean` | yes | | | `results[].lines` | `integer[]` | yes | Lines of the new source this operation wrote, or the lines it removed from. | | `results[].message` | `string` | yes | | | `results[].hint` | `string \| null` | yes | | | `valid` | `boolean` | yes | | | `kind` | `string \| null` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `errors` | `Diagnostic[]` | yes | | | `errors[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `errors[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `errors[].message` | `string` | yes | What is wrong, in one sentence. | | `errors[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `errors[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `errors[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `errors[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `warnings` | `Diagnostic[]` | yes | Quality warnings; only when the result is drawn. | | `warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `summary` | `string \| null` | yes | | | `outline` | `string[]` | yes | | | `outlineTruncated` | `boolean` | yes | | | `size` | `any` | yes | | | `size.width` | `integer` | yes | Width in px. minimum 1. | | `size.height` | `integer` | yes | Height in px. minimum 1. | | `renders` | `object[]` | yes | | | `renders[].theme` | `"light" \| "dark"` | yes | | | `renders[].svg` | `string` | yes | | | `renders[].size` | `Size` | yes | Natural size of a rendered diagram in px. | | `renders[].size.width` | `integer` | yes | Width in px. minimum 1. | | `renders[].size.height` | `integer` | yes | Height in px. minimum 1. | | `picturesOmitted` | `object` | no | Present only when pictures were left out because the answer would have been too big for an AI client to read in one piece. | | `picturesOmitted.themes` | `("light" \| "dark")[]` | yes | The color schemes whose pictures were left out. | | `picturesOmitted.characters` | `integer` | yes | How many characters the pictures left out came to. | | `picturesOmitted.limit` | `integer` | yes | The most characters one answer may hold before the pictures are left out. | | `picturesOmitted.why` | `string` | yes | Why they were left out, and what to do. | | `viewUrl` | `string` | no | A link that shows a valid diagram in a browser; give it to the person who wants to look. The source is in the part after #, never sent to a server, but anyone with the link can read it. Absent when too long; next says so. | | `svgUrl` | `string` | no | The viewUrl with &download=svg: a person opens it to download the SVG. Not a file address for curl; use saveCommand. Absent when too long. | | `saveCommand` | `string` | no | Run in a shell to save the SVG into the repository: the CLI decodes the link locally, fetches nothing, and embeds the source. Set --out to the path; add --theme dark for dark. Absent when too long. | | `next` | `string` | yes | | ## EditDiagramInput Diagram source and the edits to make to it. | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | Diagram source; the first line is the header, such as flow "Title" (draw_diagram lists every kind). at least 1 character; at most 50000 characters. | | `operations` | `object[]` | yes | Applied in order. | | `operations[].op` | `"rename" \| "set" \| "add_node" \| "remove" \| "add_edge" \| "remove_edge" \| "move" \| "replace_text"` | yes | | | `operations[].target` | `string` | no | Id, label, table.column, or "header". at most 2000 characters. | | `operations[].to` | `string` | no | rename: the new name. Edges: the end. at most 2000 characters. | | `operations[].from` | `string` | no | Edges: the start. at most 2000 characters. | | `operations[].option` | `string` | no | at most 40 characters; pattern `^[a-z][A-Za-z0-9_-]*$`. | | `operations[].value` | `string \| null` | no | set: null removes the option. at most 2000 characters. | | `operations[].id` | `string` | no | at most 2000 characters. | | `operations[].label` | `string` | no | add_edge on sankey: the amount. at most 2000 characters. | | `operations[].type` | `string` | no | add_node: node type, or statement word (c4 person, tree dir). at most 2000 characters. | | `operations[].options` | `object` | no | | | `operations[].statement` | `string` | no | add_node: the whole line, for a kind with no plain node statement. at most 2000 characters. | | `operations[].into` | `string \| null` | no | A block; null is the top level. at most 2000 characters. | | `operations[].after` | `string` | no | at most 2000 characters. | | `operations[].arrow` | `"<-->" \| "-->" \| "<->" \| "..>" \| "->" \| "--"` | no | | | `operations[].find` | `string` | no | at most 2000 characters. | | `operations[].replace` | `string` | no | at most 2000 characters. | | `operations[].all` | `boolean` | no | | | `svg` | `"auto" \| "always" \| "never"` | no | never (the default) draws nothing; auto or always draws like draw_diagram. default "never". | | `themes` | `"light" \| "dark" \| "both"` | no | light, dark, or both (the default): the light picture and the dark one a dark page needs. default "both". | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## ConvertMermaidResponse The result of converting one Mermaid diagram to Plotwork. | Field | Type | Required | Description | |---|---|---|---| | `ok` | `boolean` | yes | True when `source` is valid Plotwork source. False means nothing was converted and `source` is empty: read `diagnostics`. | | `kind` | `string \| null` | yes | The Plotwork kind the diagram became, or null when it could not be converted. | | `mermaidType` | `string \| null` | yes | The Mermaid diagram type read from the first line, such as flowchart or sequenceDiagram; null when none was found. | | `source` | `string` | yes | The Plotwork source, or an empty string when nothing could be converted. | | `diagnostics` | `Diagnostic[]` | yes | Everything dropped, changed, or wrong, with a hint that says what to do. Codes start with M. Lines and columns point into the Mermaid you sent. M1xx codes are errors that stop the conversion; M2xx codes are warnings and the conversion went on. | | `diagnostics[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `diagnostics[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `diagnostics[].message` | `string` | yes | What is wrong, in one sentence. | | `diagnostics[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `diagnostics[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `diagnostics[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `diagnostics[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `stats` | `object` | yes | | | `stats.nodes` | `integer` | yes | Flow nodes written. | | `stats.edges` | `integer` | yes | Flow edges written. | | `stats.groups` | `integer` | yes | Flow groups written. | | `stats.actors` | `integer` | yes | Sequence participants written. | | `stats.messages` | `integer` | yes | Sequence messages written. | | `stats.tables` | `integer` | yes | Schema tables written. | | `stats.columns` | `integer` | yes | Schema columns written. | | `stats.relations` | `integer` | yes | Schema relations written. | | `stats.dropped` | `integer` | yes | Warnings about constructs that were dropped: styling, links, activations, and so on. | | `stats.renamed` | `integer` | yes | Ids that were not valid Plotwork ids and were renamed. | | `validation` | `object \| null` | yes | The Plotwork validator run on `source`; null when nothing was converted. | | `validation.valid` | `boolean` | yes | True when the converted source has no Plotwork errors. It always is when `ok` is true. | | `validation.errors` | `Diagnostic[]` | yes | Plotwork errors in the converted source. Lines and columns point into `source`, not into the Mermaid. | | `validation.errors[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `validation.errors[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `validation.errors[].message` | `string` | yes | What is wrong, in one sentence. | | `validation.errors[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `validation.errors[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `validation.errors[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `validation.errors[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `validation.warnings` | `Diagnostic[]` | yes | Plotwork quality warnings, including icon and group-type suggestions with the exact icon= to write. Lines and columns point into `source`, not into the Mermaid. | | `validation.warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `validation.warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `validation.warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `validation.warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `validation.warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `validation.warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `validation.warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `validation.summary` | `string \| null` | yes | One-line description of the converted diagram, usable as alt text. | | `validation.size` | `object \| null` | yes | Natural size of the laid-out diagram. | | `validation.size.width` | `integer` | yes | Width in px. minimum 1. | | `validation.size.height` | `integer` | yes | Height in px. minimum 1. | | `quality` | `object` | no | How clean the converted diagram looks and up to three changes that would help (see draw_diagram); absent when it is not valid. | | `quality.score` | `integer` | yes | 0 to 100; 100 is a picture with nothing to fix. The same diagram always gets the same score. minimum 0; maximum 100. | | `quality.grade` | `"clean" \| "fair" \| "cluttered"` | yes | clean is 85 or more, fair 60 or more, cluttered below. | | `quality.measures` | `object` | yes | What the score is made from: crossings, lineOverBox, labelOverlaps, smallestTextPx (in an 880px column), aspectRatio, emptySpace (0 to 1), longestLine (px), detour (1 is straight), the counts of boxes, lines, labels, series, and slices, and any measure the kind adds. | | `quality.hints` | `string[]` | yes | Up to three changes, the one worth most first, each naming the statement or option to edit. | | `quality.line` | `string` | yes | The grade, the score, and the first hint in one line. | | `outline` | `string[]` | yes | The converted diagram as plain text, to check it says what the Mermaid said; empty when nothing was converted. | | `outlineTruncated` | `boolean` | yes | True when the summary level cut the outline short. Call again with detail "full" to read all of it. | | `renders` | `object[]` | yes | One picture per requested color scheme, light first; empty unless `render` was true and the conversion is valid, when svg is never, and when every picture was left out (see picturesOmitted). | | `renders[].theme` | `"light" \| "dark"` | yes | The color scheme this picture is drawn in. | | `renders[].svg` | `string` | yes | Self-contained SVG markup with an accessible title and description. | | `renders[].size` | `Size` | yes | Natural size of a rendered diagram in px. | | `renders[].size.width` | `integer` | yes | Width in px. minimum 1. | | `renders[].size.height` | `integer` | yes | Height in px. minimum 1. | | `picturesOmitted` | `object` | no | Present only when pictures were left out because the answer would have been too big for an AI client to read in one piece. | | `picturesOmitted.themes` | `("light" \| "dark")[]` | yes | The color schemes whose pictures were left out. | | `picturesOmitted.characters` | `integer` | yes | How many characters the pictures left out came to. | | `picturesOmitted.limit` | `integer` | yes | The most characters one answer may hold before the pictures are left out. | | `picturesOmitted.why` | `string` | yes | Why they were left out, and what to do. | | `viewUrl` | `string` | no | A link that shows a valid diagram in a browser; give it to the person who wants to look. The source is in the part after #, never sent to a server, but anyone with the link can read it. Absent when too long; next says so. | | `pngUrl` | `string` | no | The viewUrl with &download=png: opening it in a browser saves the diagram as a PNG for Slack, email, or a wiki. The PNG is drawn in the person's browser, not on a server, so it is a link to open, not an image address to embed. Absent when too long. | | `svgUrl` | `string` | no | The viewUrl with &download=svg: a person opens it to download the SVG. Not a file address for curl; use saveCommand. Absent when too long. | | `saveCommand` | `string` | no | Run in a shell to save the SVG into the repository: the CLI decodes the link locally, fetches nothing, and embeds the source. Set --out to the path; add --theme dark for dark. Absent when too long. | | `next` | `string` | yes | The next step in one sentence. | ## ConvertMermaidInput Mermaid source and what to return with the converted Plotwork source. | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | One Mermaid diagram: a flowchart or graph, sequenceDiagram, erDiagram, gitGraph, gantt, classDiagram, stateDiagram, timeline, journey, mindmap, treemap-beta or treemap, pie, quadrantChart, C4Context or C4Container or C4Component or C4Dynamic or C4Deployment, sankey-beta or sankey, xychart-beta or xychart, or treeView-beta or treeView. Front matter and %%{init}%% directives are allowed and are dropped. Send the text inside a mermaid fence, without the fence. at least 1 character; at most 50000 characters. | | `lenient` | `boolean` | no | false (the default): a line that cannot be read stops the conversion, so nothing is converted with a line missing. true: such a line is skipped with a warning and the rest is converted. default false. | | `render` | `boolean` | no | Also draw the converted diagram, in the color schemes themes names. default false. | | `themes` | `"light" \| "dark" \| "both"` | no | light, dark, or both (the default): the light picture and the dark one a dark page needs. default "both". | | `detail` | `"summary" \| "full"` | no | summary (the default), or full: the whole outline, and the source embedded in each SVG. default "summary". | | `svg` | `"auto" \| "always" \| "never"` | no | auto (the default) returns the SVGs, but over MCP leaves them out when the answer would be too big for a client (picturesOmitted). always returns them at any size. never draws nothing, which is cheaper. The HTTP API treats auto as always. default "auto". | | `embedSource` | `boolean` | no | Write the source into each SVG so read_diagram can give it back. Set it when you save the SVG in a repository or publish it to edit later; anyone with the file or link can then read the source. default false. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## ValidateResponse Result of checking diagram source. | Field | Type | Required | Description | |---|---|---|---| | `valid` | `boolean` | yes | True when there are no errors; warnings do not make a diagram invalid. | | `kind` | `string \| null` | yes | The diagram kind, or null when the header could not be read. | | `errors` | `Diagnostic[]` | yes | Every error found in one pass, so they can be fixed together. | | `errors[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `errors[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `errors[].message` | `string` | yes | What is wrong, in one sentence. | | `errors[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `errors[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `errors[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `errors[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `warnings` | `Diagnostic[]` | yes | Quality warnings for a valid diagram, such as too dense or too wide. | | `warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `summary` | `string \| null` | yes | One-line description of the diagram, or null when invalid. | | `size` | `object \| null` | yes | Natural rendered size, or null when invalid. | | `size.width` | `integer` | yes | Width in px. minimum 1. | | `size.height` | `integer` | yes | Height in px. minimum 1. | | `quality` | `object` | no | How clean the laid-out diagram looks, with up to three changes; absent when invalid. | | `quality.score` | `integer` | yes | 0 to 100; 100 is a picture with nothing to fix. The same diagram always gets the same score. minimum 0; maximum 100. | | `quality.grade` | `"clean" \| "fair" \| "cluttered"` | yes | clean is 85 or more, fair 60 or more, cluttered below. | | `quality.measures` | `object` | yes | What the score is made from: crossings, lineOverBox, labelOverlaps, smallestTextPx (in an 880px column), aspectRatio, emptySpace (0 to 1), longestLine (px), detour (1 is straight), the counts of boxes, lines, labels, series, and slices, and any measure the kind adds. | | `quality.hints` | `string[]` | yes | Up to three changes, the one worth most first, each naming the statement or option to edit. | | `quality.line` | `string` | yes | The grade, the score, and the first hint in one line. | | `notices` | `Note[]` | no | Information notes, such as an icon alias resolved to a real icon; absent when there is none. | | `notices[].code` | `string` | yes | A stable code, such as I122 for an icon alias. | | `notices[].severity` | `"info"` | yes | A note never stops a drawing and is not a warning. | | `notices[].message` | `string` | yes | | | `notices[].line` | `integer` | yes | minimum 1. | | `notices[].column` | `integer` | yes | minimum 1. | | `notices[].length` | `integer` | yes | minimum 1. | | `notices[].hint` | `string \| null` | yes | The text to write instead. | ## DiagramSourceInput Request body carrying diagram source. | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | Diagram source; the first line is the header, such as flow "Title" (draw_diagram lists every kind). at least 1 character; at most 50000 characters. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## RenderResponse A rendered diagram. | Field | Type | Required | Description | |---|---|---|---| | `svg` | `string` | yes | Self-contained SVG markup with an accessible title and description. | | `theme` | `"light" \| "dark" \| "auto"` | yes | The color scheme used. | | `size` | `Size` | yes | Natural size of a rendered diagram in px. | | `size.width` | `integer` | yes | Width in px. minimum 1. | | `size.height` | `integer` | yes | Height in px. minimum 1. | | `warnings` | `Diagnostic[]` | yes | | | `warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | ## RenderInput Diagram source and rendering options. | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | Diagram source; the first line is the header, such as flow "Title" (draw_diagram lists every kind). at least 1 character; at most 50000 characters. | | `theme` | `"light" \| "dark" \| "auto"` | no | Color scheme to paint with: light, dark, or auto for one picture that follows the reader's light or dark setting. default "light". | | `idPrefix` | `string` | no | Prefix for element ids; set it when inlining several diagrams into one page. at most 32 characters; pattern `^[A-Za-z][A-Za-z0-9_-]*$`. | | `embedSource` | `boolean` | no | Write the source into the SVG metadata so read_diagram can give it back. Set it true when you save the SVG into a repository; anyone with the file can then read the source. Default false. default false. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## OutlineResponse A plain-text description of a diagram, usable as alt text. | Field | Type | Required | Description | |---|---|---|---| | `kind` | `string` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `summary` | `string` | yes | One-line description. | | `outline` | `string[]` | yes | Lines in reading order: participants, steps, tables, and connections. | ## ReadDiagramResponse The source of a Plotwork SVG, or why there is none and what to do. | Field | Type | Required | Description | |---|---|---|---| | `status` | `"read" \| "no-source" \| "damaged" \| "not-a-picture" \| "refused" \| "unreachable"` | yes | read: the source was found. no-source: the picture carries none. damaged: it carries one that cannot be read back. not-a-picture: the text is not an SVG. refused: the link is not one this tool fetches. unreachable: the link could not be fetched. | | `hasSource` | `boolean` | yes | True when source is set. | | `source` | `string \| null` | yes | The exact source the picture was drawn from, ready to edit and send to draw_diagram; null when there is none. | | `kind` | `string \| null` | yes | The diagram kind of the source, or null. | | `id` | `string \| null` | yes | The id of the stored diagram when the picture was published (data-plotwork-id), or null. | | `from` | `"text" \| "url"` | yes | Where the picture came from: the text that was sent, or the link that was fetched. | | `validation` | `object \| null` | yes | The check of the source, or null when there is none. It parses the source; draw_diagram also lays it out and returns warnings. | | `validation.valid` | `boolean` | yes | True when the source has no errors. | | `validation.errors` | `Diagnostic[]` | yes | Every error, each with line, column, and a hint; empty when valid. | | `validation.errors[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `validation.errors[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `validation.errors[].message` | `string` | yes | What is wrong, in one sentence. | | `validation.errors[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `validation.errors[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `validation.errors[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `validation.errors[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `validation.summary` | `string \| null` | yes | One-line description of the diagram, or null when invalid. | | `picture` | `object \| null` | yes | What the picture says about itself, or null when the text is not an SVG. | | `picture.title` | `string \| null` | yes | The diagram title, or null. | | `picture.summary` | `string \| null` | yes | The one-line summary the picture carries, or null. | | `picture.theme` | `string \| null` | yes | The color scheme it was drawn in, or null. | | `picture.size` | `object \| null` | yes | Its size in px, or null. | | `picture.size.width` | `integer` | yes | Width in px. minimum 1. | | `picture.size.height` | `integer` | yes | Height in px. minimum 1. | | `picture.outline` | `string[]` | yes | The plain-language outline the picture carries, one entry for each line. Empty when the source was read: the source says more. | | `picture.labels` | `string[]` | yes | The text painted on the picture, each distinct label once, in painted order. Empty when the source was read. | | `message` | `string` | yes | What happened, in one sentence. | | `next` | `string` | yes | The next step in one sentence. | ## ReadDiagramInput The text of a Plotwork SVG, or the link of a hosted one. | Field | Type | Required | Description | |---|---|---|---| | `svg` | `string` | no | The text of a Plotwork SVG: the content of an .svg file in the repository. Send this or url, not both. at least 1 character; at most 1000000 characters. | | `url` | `string` | no | The link of a hosted Plotwork picture, https://cdn.plotwork.dev/d/<22 characters>.svg. Only Plotwork's own picture hosts are fetched. Send this or svg, not both. at least 1 character; at most 200 characters. | ## CompareDiagramsResponse What changed between two versions of a diagram. | Field | Type | Required | Description | |---|---|---|---| | `same` | `boolean` | yes | | | `before` | `object` | yes | | | `before.from` | `"source" \| "svg" \| "url"` | yes | | | `before.read` | `boolean` | yes | False when no source could be read from it; message says why. | | `before.kind` | `string \| null` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `before.valid` | `boolean` | yes | | | `before.message` | `string \| null` | yes | | | `after` | `object` | yes | | | `after.from` | `"source" \| "svg" \| "url"` | yes | | | `after.read` | `boolean` | yes | False when no source could be read from it; message says why. | | `after.kind` | `string \| null` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `after.valid` | `boolean` | yes | | | `after.message` | `string \| null` | yes | | | `counts` | `object` | yes | | | `counts.added` | `integer` | yes | | | `counts.removed` | `integer` | yes | | | `counts.renamed` | `integer` | yes | | | `counts.moved` | `integer` | yes | | | `counts.changed` | `integer` | yes | | | `changes` | `object[]` | yes | | | `changes[].change` | `"added" \| "removed" \| "renamed" \| "moved" \| "changed"` | yes | | | `changes[].subject` | `"header" \| "node" \| "group" \| "edge" \| "row" \| "series" \| "line"` | yes | | | `changes[].name` | `string` | yes | The id, the label in quotes, or for an edge its ends. | | `changes[].line` | `integer \| null` | yes | The line in after, or in before for a removal. | | `changes[].detail` | `string` | yes | | | `truncated` | `boolean` | yes | True when more changes were found than are listed; counts covers all of them. | | `summary` | `string` | yes | The changes in plain words, for a pull request review. | | `markdown` | `string \| null` | yes | | | `next` | `string` | yes | | ## CompareDiagramsInput Two versions of a diagram to compare. | Field | Type | Required | Description | |---|---|---|---| | `before` | `string` | yes | The old diagram: source, the text of a Plotwork SVG that carries its source, or a cdn.plotwork.dev link. at least 1 character; at most 1000000 characters. | | `after` | `string` | yes | The new diagram, in any of the same three forms. at least 1 character; at most 1000000 characters. | | `markdown` | `boolean` | no | Also return a Markdown block ready to paste as a pull request comment. default false. | ## SuggestDiagramResponse The diagram kinds that fit a request, best first, each with a starter source. | Field | Type | Required | Description | |---|---|---|---| | `request` | `string` | yes | The request that was read. | | `suggestions` | `object[]` | yes | Up to 3, best first, one for each kind and style. | | `suggestions[].kind` | `string` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `suggestions[].style` | `string \| null` | yes | The style the starter uses: compare, score, raci, or risk for a matrix, files or org for a tree; null for the kind's only style. | | `suggestions[].why` | `string` | yes | One sentence: why this kind fits. | | `suggestions[].confidence` | `"high" \| "medium" \| "low"` | yes | high: the text names this kind of picture or its shape fits it exactly. medium: it leans toward it. low: a fallback worth considering. | | `suggestions[].starter` | `string` | yes | A short Plotwork source of this kind that parses without errors: built from the names and numbers in the text where it could find them, otherwise a small example to edit. Replace every placeholder before you draw it. | | `suggestions[].tailored` | `boolean` | yes | True when the starter uses names or numbers from the text; false when it is the kind's small example. | | `suggestions[].statements` | `string[]` | yes | The few statements of this kind to write, as syntax. get_diagram_syntax with this kind has them all. | | `importFormat` | `string` | no | Present when the data is, or the request names, a file import_source reads (sql, openapi, compose, terraform, and so on): call import_source with this format and the file's text instead of editing a starter. | | `next` | `string` | yes | The next step in one sentence. | ## SuggestDiagramInput A request in plain words and optional data to draw from. | Field | Type | Required | Description | |---|---|---|---| | `request` | `string` | yes | What the picture is for, in plain words, such as "permission matrix for these endpoints and roles" or "how a refund moves between support and billing". at least 1 character; at most 1000 characters. | | `data` | `string` | no | Optional text to draw from: a pasted table, a list, numbers by period, or notes. Names and numbers in it are used in the starters. at most 20000 characters. | ## EnquiryAck | Field | Type | Required | Description | |---|---|---|---| | `received` | `true` | yes | Always true. The enquiry was received. | ## EnquiryBody | Field | Type | Required | Description | |---|---|---|---| | `name` | `string` | yes | The name of the person asking. at least 1 character; at most 120 characters. | | `email` | `string` | yes | Their work email address. The reply goes here. | | `company` | `string` | yes | The company. at least 1 character; at most 120 characters. | | `companySize` | `"1-49" \| "50-249" \| "250-999" \| "1000-4999" \| "5000+"` | yes | How many people the company has. | | `repos` | `"1-9" \| "10-49" \| "50-199" \| "200-999" \| "1000+"` | yes | How many repositories the team would run diagrams in. | | `useCase` | `string` | yes | What the team wants to do, in their words. at least 10 characters; at most 2000 characters. | | `interests` | `("sso-scim" \| "audit-siem" \| "support-sla" \| "limits" \| "self-hosting" \| "invoicing")[]` | no | What the company needs, as the boxes ticked on the form. Left out, nothing was ticked; a box named twice counts once. | | `context` | `EnquiryContext` | no | Sent by the portal: where the form was opened. The site sends none. | | `context.page` | `"contact" \| "plan" \| "sso" \| "integrations" \| "support" \| "audit"` | yes | The place in the portal the form was opened from, such as `sso` for the Single sign-on panel. | | `context.org` | `string` | no | The slug of the organisation the form was opened from. Kept only when the signed-in account is a member. pattern `^[a-z0-9](?:[a-z0-9-]{1,38}[a-z0-9])$`. | | `website` | `string` | no | Leave empty. A field people do not see: an enquiry that fills it is taken for a script and dropped, with the same answer. at most 200 characters. | | `elapsedMs` | `integer` | yes | Milliseconds between the form being shown and being sent. An enquiry sent faster than a person can type is dropped, with the same answer. minimum 0. | ## EnquiryContext Sent by the portal: where the form was opened. The site sends none. | Field | Type | Required | Description | |---|---|---|---| | `page` | `"contact" \| "plan" \| "sso" \| "integrations" \| "support" \| "audit"` | yes | The place in the portal the form was opened from, such as `sso` for the Single sign-on panel. | | `org` | `string` | no | The slug of the organisation the form was opened from. Kept only when the signed-in account is a member. pattern `^[a-z0-9](?:[a-z0-9-]{1,38}[a-z0-9])$`. | ## PublishAnswer | Field | Type | Required | Description | |---|---|---|---| ## PublishedDiagram | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The handle for managing the diagram (delete). It is not in the link. | | `url` | `string` | yes | The public address of the SVG. Anyone with the link can see it; the token in it cannot be guessed. | | `name` | `string \| null` | yes | The label given at publish, or null. | | `folder` | `string \| null` | yes | The folder it is filed in, such as `Payments/Checkout`, or null for the top level. | | `theme` | `"light" \| "dark" \| "auto"` | yes | The color scheme it was drawn in: light, dark, or auto. | | `bytes` | `integer` | yes | Size of the stored SVG in bytes. | | `createdAt` | `integer` | yes | When it was first published, in milliseconds since the epoch. | | `publishedAt` | `integer` | yes | When it was last published, in milliseconds since the epoch. Publishing the same diagram again moves it. | | `expiresAt` | `integer \| null` | yes | When it will be deleted, in milliseconds since the epoch, or null when the plan keeps diagrams until you delete them. | | `locked` | `boolean` | yes | True while the diagram is locked: it cannot be deleted, renamed or moved until you unlock it. A lock does not stop it expiring on a plan that expires diagrams. | | `lockedAt` | `integer \| null` | yes | When it was locked, in milliseconds since the epoch, or null when it is not locked. | | `created` | `boolean` | yes | True when this call stored a new diagram; false when the same diagram was already hosted and its link was kept. | | `stableLink` | `StableLink` | no | Present only when the publish named an alias: the stable link, now pointing at this diagram. | | `stableLink.id` | `string` | yes | The handle for managing the stable link (delete). It is not in its address. | | `stableLink.alias` | `string` | yes | The name you gave it. Only you see it. | | `stableLink.url` | `string` | yes | The public address, on the CDN host. It shows the diagram the stable link points at now (cached for up to 60 seconds) and never changes. Anyone with it can see the picture. | | `stableLink.diagramId` | `string \| null` | yes | The diagram it shows now, or null when that diagram was deleted or expired; the address then answers 404 until you point the link at a diagram again. | | `stableLink.createdAt` | `integer` | yes | When it was made, in milliseconds since the epoch. | | `stableLink.updatedAt` | `integer` | yes | When it was last pointed at a diagram, in milliseconds since the epoch. | ## StableLink Present only when the publish named an alias: the stable link, now pointing at this diagram. | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The handle for managing the stable link (delete). It is not in its address. | | `alias` | `string` | yes | The name you gave it. Only you see it. | | `url` | `string` | yes | The public address, on the CDN host. It shows the diagram the stable link points at now (cached for up to 60 seconds) and never changes. Anyone with it can see the picture. | | `diagramId` | `string \| null` | yes | The diagram it shows now, or null when that diagram was deleted or expired; the address then answers 404 until you point the link at a diagram again. | | `createdAt` | `integer` | yes | When it was made, in milliseconds since the epoch. | | `updatedAt` | `integer` | yes | When it was last pointed at a diagram, in milliseconds since the epoch. | ## HostedDiagram | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The handle for managing the diagram (delete). It is not in the link. | | `url` | `string` | yes | The public address of the SVG. Anyone with the link can see it; the token in it cannot be guessed. | | `name` | `string \| null` | yes | The label given at publish, or null. | | `folder` | `string \| null` | yes | The folder it is filed in, such as `Payments/Checkout`, or null for the top level. | | `theme` | `"light" \| "dark" \| "auto"` | yes | The color scheme it was drawn in: light, dark, or auto. | | `bytes` | `integer` | yes | Size of the stored SVG in bytes. | | `createdAt` | `integer` | yes | When it was first published, in milliseconds since the epoch. | | `publishedAt` | `integer` | yes | When it was last published, in milliseconds since the epoch. Publishing the same diagram again moves it. | | `expiresAt` | `integer \| null` | yes | When it will be deleted, in milliseconds since the epoch, or null when the plan keeps diagrams until you delete them. | | `locked` | `boolean` | yes | True while the diagram is locked: it cannot be deleted, renamed or moved until you unlock it. A lock does not stop it expiring on a plan that expires diagrams. | | `lockedAt` | `integer \| null` | yes | When it was locked, in milliseconds since the epoch, or null when it is not locked. | ## RoutedPublish | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The handle for the diagram in the routes below. | | `name` | `string \| null` | yes | The label given at publish, or null. | | `folder` | `string \| null` | yes | The folder it is filed in, such as `Payments/Checkout`, or null for the top level. | | `theme` | `"light" \| "dark" \| "auto"` | yes | The color scheme it was drawn in: light, dark, or auto. | | `bytes` | `integer` | yes | Size of the stored SVG in bytes. | | `createdBy` | `string \| null` | yes | The account that published it, or null when that account has left or an organisation key published it. | | `createdAt` | `integer` | yes | When it was first published, in milliseconds since the epoch. | | `publishedAt` | `integer` | yes | When it was last published, in milliseconds since the epoch. Publishing the same diagram again moves it. | | `expiresAt` | `integer \| null` | yes | When it will be deleted, in milliseconds since the epoch, or null when the owner's plan keeps diagrams until they are deleted. | | `viewUrl` | `string` | yes | A page in the portal that shows the diagram to a signed-in member. It is not a link anyone can open: an organisation's diagrams have no public address. | | `locked` | `boolean` | yes | True while the owner has the diagram locked: nobody can delete, rename or move it, its publisher and the owner included, until the owner unlocks it. A lock does not stop it expiring on a plan that expires diagrams. | | `lockedAt` | `integer \| null` | yes | When the owner locked it, in milliseconds since the epoch, or null when it is not locked. | | `restricted` | `true` | no | Present, and true, when the owner has restricted this diagram's folder and the caller is allowed in it, so the portal can show it as locked. Absent otherwise. A diagram in a folder the caller may not use is not listed at all. | | `created` | `boolean` | yes | True when this call stored a new diagram; false when the same diagram was already in the library and was kept. | | `sourceEmbedded` | `boolean` | yes | True when the stored picture carries the diagram source: the call sent embedSource true, or left it out while the organisation's keepEditable is on. | | `private` | `true` | yes | The diagram is in an organisation's private library and has no public link. | | `organization` | `PublishOrganization` | yes | The organisation whose private library holds the diagram. | | `organization.id` | `string` | yes | | | `organization.slug` | `string` | yes | | | `organization.name` | `string` | yes | | | `routedBy` | `"organisation-default" \| "organisation-required"` | no | Present when the organisation's Publishing setting chose its library for a publish that named no organisation: organisation-default (its members publish there unless they ask for a personal link) or organisation-required (they always do). | ## PublishOrganization The organisation whose private library holds the diagram. | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `slug` | `string` | yes | | | `name` | `string` | yes | | ## PublishedOrganisationDiagram | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The handle for the diagram in the routes below. | | `name` | `string \| null` | yes | The label given at publish, or null. | | `folder` | `string \| null` | yes | The folder it is filed in, such as `Payments/Checkout`, or null for the top level. | | `theme` | `"light" \| "dark" \| "auto"` | yes | The color scheme it was drawn in: light, dark, or auto. | | `bytes` | `integer` | yes | Size of the stored SVG in bytes. | | `createdBy` | `string \| null` | yes | The account that published it, or null when that account has left or an organisation key published it. | | `createdAt` | `integer` | yes | When it was first published, in milliseconds since the epoch. | | `publishedAt` | `integer` | yes | When it was last published, in milliseconds since the epoch. Publishing the same diagram again moves it. | | `expiresAt` | `integer \| null` | yes | When it will be deleted, in milliseconds since the epoch, or null when the owner's plan keeps diagrams until they are deleted. | | `viewUrl` | `string` | yes | A page in the portal that shows the diagram to a signed-in member. It is not a link anyone can open: an organisation's diagrams have no public address. | | `locked` | `boolean` | yes | True while the owner has the diagram locked: nobody can delete, rename or move it, its publisher and the owner included, until the owner unlocks it. A lock does not stop it expiring on a plan that expires diagrams. | | `lockedAt` | `integer \| null` | yes | When the owner locked it, in milliseconds since the epoch, or null when it is not locked. | | `restricted` | `true` | no | Present, and true, when the owner has restricted this diagram's folder and the caller is allowed in it, so the portal can show it as locked. Absent otherwise. A diagram in a folder the caller may not use is not listed at all. | | `created` | `boolean` | yes | True when this call stored a new diagram; false when the same diagram was already in the library and was kept. | | `sourceEmbedded` | `boolean` | yes | True when the stored picture carries the diagram source: the call sent embedSource true, or left it out while the organisation's keepEditable is on. | ## OrganisationDiagram | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The handle for the diagram in the routes below. | | `name` | `string \| null` | yes | The label given at publish, or null. | | `folder` | `string \| null` | yes | The folder it is filed in, such as `Payments/Checkout`, or null for the top level. | | `theme` | `"light" \| "dark" \| "auto"` | yes | The color scheme it was drawn in: light, dark, or auto. | | `bytes` | `integer` | yes | Size of the stored SVG in bytes. | | `createdBy` | `string \| null` | yes | The account that published it, or null when that account has left or an organisation key published it. | | `createdAt` | `integer` | yes | When it was first published, in milliseconds since the epoch. | | `publishedAt` | `integer` | yes | When it was last published, in milliseconds since the epoch. Publishing the same diagram again moves it. | | `expiresAt` | `integer \| null` | yes | When it will be deleted, in milliseconds since the epoch, or null when the owner's plan keeps diagrams until they are deleted. | | `viewUrl` | `string` | yes | A page in the portal that shows the diagram to a signed-in member. It is not a link anyone can open: an organisation's diagrams have no public address. | | `locked` | `boolean` | yes | True while the owner has the diagram locked: nobody can delete, rename or move it, its publisher and the owner included, until the owner unlocks it. A lock does not stop it expiring on a plan that expires diagrams. | | `lockedAt` | `integer \| null` | yes | When the owner locked it, in milliseconds since the epoch, or null when it is not locked. | | `restricted` | `true` | no | Present, and true, when the owner has restricted this diagram's folder and the caller is allowed in it, so the portal can show it as locked. Absent otherwise. A diagram in a folder the caller may not use is not listed at all. | ## PublishDiagramBody | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | Diagram source; the first line is the header, such as flow "Title" (draw_diagram lists every kind). at least 1 character; at most 50000 characters. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | | `theme` | `"light" \| "dark" \| "auto"` | no | The color scheme to draw it in: light, dark, or auto (one picture that follows the viewer's light or dark setting). default "light". | | `embedSource` | `boolean` | no | Write the diagram source into the stored SVG, in its metadata, so the picture can be edited later: read_diagram gives the source back from the link. The source then becomes part of the picture, which anyone who has the link can read (for a diagram in an organisation, every member who can open it). The same diagram published with and without it is two pictures with two links. Left out, a personal diagram does not store the source, and a diagram that lands in an organisation follows its keepEditable setting. | | `name` | `string` | no | A label for the diagram in your list and in the Markdown snippet. Optional. at least 1 character; at most 80 characters; pattern `^[^\p{Cc}]+$/u`. | | `folder` | `string \| null` | no | The folder to file the diagram in. Left out, a new diagram goes to the top level and a diagram that is already hosted stays where it is. A path files a new diagram there, or moves the one that is already hosted and keeps its link. null puts it at the top level. | | `alias` | `string` | no | Point a stable link with this name at the diagram, so one address always shows the latest version. The first publish with a name makes the stable link (the answer's stableLink.url) and every later publish with the same name moves it to the new diagram; the address never changes. How many stable links an account keeps depends on its plan. An alias asks for a personal link, so an organisation that requires its library refuses it (orgs.publish_required). Optional. at least 1 character; at most 60 characters; pattern `^[a-z0-9]+(?:-[a-z0-9]+)*$`. | | `organization` | `string` | no | An organisation's slug or id: the diagram goes to that organisation's private library, and the answer has private true and a viewUrl in place of a public link. The account must be a member who may publish there. Left out, the diagram goes to your own library, unless an organisation you belong to publishes its members' diagrams to its library. at least 1 character; at most 64 characters. | | `personal` | `boolean` | no | True asks for a public link in your own library even though an organisation you belong to publishes its members' diagrams to its library by default. An organisation that requires its library refuses it (403 orgs.publish_required). Not with organization. | ## HostedDiagramList | Field | Type | Required | Description | |---|---|---|---| | `items` | `HostedDiagram[]` | yes | At most 200, newest published first. | | `items[].id` | `string` | yes | The handle for managing the diagram (delete). It is not in the link. | | `items[].url` | `string` | yes | The public address of the SVG. Anyone with the link can see it; the token in it cannot be guessed. | | `items[].name` | `string \| null` | yes | The label given at publish, or null. | | `items[].folder` | `string \| null` | yes | The folder it is filed in, such as `Payments/Checkout`, or null for the top level. | | `items[].theme` | `"light" \| "dark" \| "auto"` | yes | The color scheme it was drawn in: light, dark, or auto. | | `items[].bytes` | `integer` | yes | Size of the stored SVG in bytes. | | `items[].createdAt` | `integer` | yes | When it was first published, in milliseconds since the epoch. | | `items[].publishedAt` | `integer` | yes | When it was last published, in milliseconds since the epoch. Publishing the same diagram again moves it. | | `items[].expiresAt` | `integer \| null` | yes | When it will be deleted, in milliseconds since the epoch, or null when the plan keeps diagrams until you delete them. | | `items[].locked` | `boolean` | yes | True while the diagram is locked: it cannot be deleted, renamed or moved until you unlock it. A lock does not stop it expiring on a plan that expires diagrams. | | `items[].lockedAt` | `integer \| null` | yes | When it was locked, in milliseconds since the epoch, or null when it is not locked. | | `stableLinks` | `StableLink[]` | yes | Every stable link the account keeps, by alias, including any whose diagram is gone. | | `stableLinks[].id` | `string` | yes | The handle for managing the stable link (delete). It is not in its address. | | `stableLinks[].alias` | `string` | yes | The name you gave it. Only you see it. | | `stableLinks[].url` | `string` | yes | The public address, on the CDN host. It shows the diagram the stable link points at now (cached for up to 60 seconds) and never changes. Anyone with it can see the picture. | | `stableLinks[].diagramId` | `string \| null` | yes | The diagram it shows now, or null when that diagram was deleted or expired; the address then answers 404 until you point the link at a diagram again. | | `stableLinks[].createdAt` | `integer` | yes | When it was made, in milliseconds since the epoch. | | `stableLinks[].updatedAt` | `integer` | yes | When it was last pointed at a diagram, in milliseconds since the epoch. | | `usage` | `HostingUsage` | yes | | | `usage.plan` | `"free" \| "pro"` | yes | The plan the account is on, which decides the limits. | | `usage.count` | `integer` | yes | Hosted diagrams the account holds. This is the total, even when the list is shorter. minimum 0. | | `usage.bytes` | `integer` | yes | Total size of the hosted diagrams in bytes. minimum 0. | | `usage.stableLinks` | `integer` | yes | Stable links the account keeps. minimum 0. | | `usage.limits` | `HostingPlanLimits` | yes | | | `usage.limits.dailyCalls` | `integer` | yes | | | `usage.limits.hostedDiagrams` | `integer` | yes | | | `usage.limits.hostedBytes` | `integer` | yes | | | `usage.limits.retentionDays` | `integer \| null` | yes | Days a diagram is kept after it was last published, or null to keep it until you delete it. | | `usage.limits.maxDiagramBytes` | `integer` | yes | | | `usage.limits.stableLinks` | `integer` | yes | Stable links the account may keep at once. | ## HostingUsage | Field | Type | Required | Description | |---|---|---|---| | `plan` | `"free" \| "pro"` | yes | The plan the account is on, which decides the limits. | | `count` | `integer` | yes | Hosted diagrams the account holds. This is the total, even when the list is shorter. minimum 0. | | `bytes` | `integer` | yes | Total size of the hosted diagrams in bytes. minimum 0. | | `stableLinks` | `integer` | yes | Stable links the account keeps. minimum 0. | | `limits` | `HostingPlanLimits` | yes | | | `limits.dailyCalls` | `integer` | yes | | | `limits.hostedDiagrams` | `integer` | yes | | | `limits.hostedBytes` | `integer` | yes | | | `limits.retentionDays` | `integer \| null` | yes | Days a diagram is kept after it was last published, or null to keep it until you delete it. | | `limits.maxDiagramBytes` | `integer` | yes | | | `limits.stableLinks` | `integer` | yes | Stable links the account may keep at once. | ## HostingPlanLimits | Field | Type | Required | Description | |---|---|---|---| | `dailyCalls` | `integer` | yes | | | `hostedDiagrams` | `integer` | yes | | | `hostedBytes` | `integer` | yes | | | `retentionDays` | `integer \| null` | yes | Days a diagram is kept after it was last published, or null to keep it until you delete it. | | `maxDiagramBytes` | `integer` | yes | | | `stableLinks` | `integer` | yes | Stable links the account may keep at once. | ## MoveDiagramBody | Field | Type | Required | Description | |---|---|---|---| | `folder` | `string \| null` | yes | The folder to file the diagram in, or null for the top level. | ## EditedDiagram | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The handle for managing the diagram (delete). It is not in the link. | | `url` | `string` | yes | The public address of the SVG. Anyone with the link can see it; the token in it cannot be guessed. | | `name` | `string \| null` | yes | The label given at publish, or null. | | `folder` | `string \| null` | yes | The folder it is filed in, such as `Payments/Checkout`, or null for the top level. | | `theme` | `"light" \| "dark" \| "auto"` | yes | The color scheme it was drawn in: light, dark, or auto. | | `bytes` | `integer` | yes | Size of the stored SVG in bytes. | | `createdAt` | `integer` | yes | When it was first published, in milliseconds since the epoch. | | `publishedAt` | `integer` | yes | When it was last published, in milliseconds since the epoch. Publishing the same diagram again moves it. | | `expiresAt` | `integer \| null` | yes | When it will be deleted, in milliseconds since the epoch, or null when the plan keeps diagrams until you delete them. | | `locked` | `boolean` | yes | True while the diagram is locked: it cannot be deleted, renamed or moved until you unlock it. A lock does not stop it expiring on a plan that expires diagrams. | | `lockedAt` | `integer \| null` | yes | When it was locked, in milliseconds since the epoch, or null when it is not locked. | | `warnings` | `Diagnostic[]` | yes | Quality warnings from drawing the source, as draw_diagram gives them. Empty for a restore. | | `warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `changed` | `boolean` | yes | False when the source draws the picture the diagram already has: nothing was stored and no version was kept. | ## SaveDiagramSourceBody | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | Diagram source; the first line is the header, such as flow "Title" (draw_diagram lists every kind). at least 1 character; at most 50000 characters. | | `theme` | `"light" \| "dark" \| "auto"` | no | The color scheme to draw it in: light, dark, or auto. Left out, the theme the diagram has. | | `baseVersion` | `integer` | yes | The diagram's publishedAt (milliseconds since the epoch) when the editor loaded it. If the diagram was saved since, the answer is 409 diagram.version_conflict with details.currentVersion. minimum 0. | ## DiagramVersionList | Field | Type | Required | Description | |---|---|---|---| | `items` | `DiagramVersion[]` | yes | Newest first. A diagram keeps at most ten. | | `items[].id` | `string` | yes | The handle for restoring it. | | `items[].savedAt` | `integer` | yes | When it stopped being the current picture, in milliseconds since the epoch. | | `items[].bytes` | `integer` | yes | Size of the stored SVG in bytes. Versions do not count toward the plan. | | `items[].theme` | `"light" \| "dark" \| "auto"` | yes | | ## DiagramVersion | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The handle for restoring it. | | `savedAt` | `integer` | yes | When it stopped being the current picture, in milliseconds since the epoch. | | `bytes` | `integer` | yes | Size of the stored SVG in bytes. Versions do not count toward the plan. | | `theme` | `"light" \| "dark" \| "auto"` | yes | | ## RestoreDiagramVersionBody | Field | Type | Required | Description | |---|---|---|---| | `baseVersion` | `integer` | yes | The diagram's publishedAt (milliseconds since the epoch) when the editor loaded it. If the diagram was saved since, the answer is 409 diagram.version_conflict with details.currentVersion. minimum 0. | ## SetStableLinkBody | Field | Type | Required | Description | |---|---|---|---| | `alias` | `string` | yes | The name of a stable link: lower-case letters and digits in words joined by single hyphens, up to 60 characters. Only you see it; the public address carries a random token instead. at least 1 character; at most 60 characters; pattern `^[a-z0-9]+(?:-[a-z0-9]+)*$`. | | `diagramId` | `string` | yes | One of your hosted diagrams, by its id. | ## OEmbedPhoto | Field | Type | Required | Description | |---|---|---|---| | `version` | `"1.0"` | yes | | | `type` | `"photo"` | yes | | | `provider_name` | `string` | yes | | | `provider_url` | `string` | yes | | | `title` | `string` | yes | Always "Diagram": the diagram's own name is shown only to its owner. | | `url` | `string` | yes | The picture. For a stable link, the stable link itself, so the embed keeps showing the latest version. | | `width` | `integer` | yes | | | `height` | `integer` | yes | | | `cache_age` | `integer` | yes | Seconds the answer may be kept: 60 for a stable link, 3600 for a picture. | ## MapIconsResponse An icon, or candidates, for each name sent, and the names that found no icon. | Field | Type | Required | Description | |---|---|---|---| | `provider` | `"aws" \| "gcp" \| "azure" \| "alibaba" \| "oci" \| "ibm" \| "cloudflare" \| "digitalocean" \| "vercel" \| null` | yes | The provider hint that was used, or null. | | `results` | `object[]` | yes | One entry per name that matched something, in the order sent. | | `results[].name` | `string` | yes | The name exactly as it was sent. | | `results[].resolved` | `boolean` | yes | True when icon holds the one icon to use. False when the name could mean more than one: choose from candidates, or ask the user. | | `results[].icon` | `string \| null` | yes | The icon to write as icon=, such as aws:lambda; null when resolved is false. | | `results[].confidence` | `"exact" \| "strong" \| "ambiguous"` | yes | exact: the name is the icon or product name. strong: the name clearly points at this icon and no other provider competes. ambiguous: several icons fit, or the match is by idea only. | | `results[].alternatives` | `string[]` | yes | Up to 3 other icons that also fit a resolved name, best first. Empty when none. | | `results[].candidates` | `string[]` | yes | When resolved is false, the icons that fit, best first (up to 4). Empty when resolved. | | `gaps` | `object[]` | yes | Names that matched nothing, in the order sent. | | `gaps[].name` | `string` | yes | The name that matched no icon. | | `gaps[].fallback` | `string` | yes | A neutral icon to use instead, or leave icon= out so the node type draws its own. | | `gaps[].reason` | `string` | yes | Why there is no match, and what to try. | | `resolvedCount` | `integer` | yes | How many names have one icon to use. | | `ambiguousCount` | `integer` | yes | How many names need a choice from candidates. | | `gapCount` | `integer` | yes | How many names matched nothing. | ## MapIconsInput Names to find icons for, and an optional provider to look in first. | Field | Type | Required | Description | |---|---|---|---| | `names` | `string[]` | yes | Services, products, or ideas to find icons for, written as the user wrote them ("S3", "Postgres", "load balancer"). Up to 40 names of up to 80 characters. | | `provider` | `"aws" \| "gcp" \| "azure" \| "alibaba" \| "oci" \| "ibm" \| "cloudflare" \| "digitalocean" \| "vercel"` | no | The cloud the architecture runs on. A name is looked up there first, so "queue" finds aws:sqs for aws. Omit it when the names are not tied to one provider. | ## IconSearchResponse Matching bundled icon names. | Field | Type | Required | Description | |---|---|---|---| | `query` | `string` | yes | The query that was searched. | | `items` | `string[]` | yes | Matching icon references as set:name, best match first. | ## ImportSourceResponse The result of turning a file of another format into Plotwork source. | Field | Type | Required | Description | |---|---|---|---| | `ok` | `boolean` | yes | True when `source` is valid Plotwork source. False means nothing was imported and `source` is empty: read `diagnostics`. | | `kind` | `string \| null` | yes | The kind of diagram the file became, or null when nothing was imported. | | `format` | `string` | yes | The format the text was read as. | | `source` | `string` | yes | The Plotwork source, or an empty string when nothing could be imported. | | `diagnostics` | `Diagnostic[]` | yes | Everything left out, changed, or wrong, with a hint. Codes start with I and point into the text you sent: I1xx errors stopped the import, I2xx warnings did not. | | `diagnostics[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `diagnostics[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `diagnostics[].message` | `string` | yes | What is wrong, in one sentence. | | `diagnostics[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `diagnostics[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `diagnostics[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `diagnostics[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `stats` | `object` | yes | | | `stats.items` | `integer` | yes | Boxes, tables, rows, slices, places, entries, or commits written. | | `stats.links` | `integer` | yes | Arrows, relations, messages, flows, or merges written. | | `stats.groups` | `integer` | yes | Frames, sections, or folders written. | | `stats.dropped` | `integer` | yes | Things in the file left out by a cap or because the picture has no place for them; each is in diagnostics. | | `coverage` | `object \| null` | yes | How much of the file the picture shows; null when nothing was imported. When complete is false, the picture is not the whole file: say so. | | `coverage.noun` | `string` | yes | What was counted, such as resources, services, tables, or endpoints. | | `coverage.total` | `integer` | yes | Everything of that kind in the file. | | `coverage.drawn` | `integer` | yes | Drawn as a box, frame, table, row, or entry of its own. | | `coverage.summarised` | `integer` | yes | Not drawn on their own but counted on the box they belong to, such as "3 role assignments". | | `coverage.omitted` | `integer` | yes | Not in the picture at all; the diagnostics say which and why. | | `coverage.complete` | `boolean` | yes | True when nothing was omitted. | | `coverage.line` | `string` | yes | The coverage in one sentence, such as "Drawn 12 of 85 resources; 60 are counted on the boxes they belong to and 13 are left out.". | | `validation` | `object \| null` | yes | The Plotwork validator run on `source`; null when nothing was imported. | | `validation.valid` | `boolean` | yes | True when the source has no Plotwork errors. It always is when `ok` is true. | | `validation.errors` | `Diagnostic[]` | yes | Plotwork errors. Lines and columns point into `source`. | | `validation.errors[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `validation.errors[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `validation.errors[].message` | `string` | yes | What is wrong, in one sentence. | | `validation.errors[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `validation.errors[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `validation.errors[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `validation.errors[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `validation.warnings` | `Diagnostic[]` | yes | Plotwork quality warnings, with the exact fix in each hint. Lines and columns point into `source`. | | `validation.warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `validation.warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `validation.warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `validation.warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `validation.warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `validation.warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `validation.warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `validation.summary` | `string \| null` | yes | One-line description of the diagram, usable as alt text. | | `validation.size` | `object \| null` | yes | Natural size of the laid-out diagram. | | `validation.size.width` | `integer` | yes | Width in px. minimum 1. | | `validation.size.height` | `integer` | yes | Height in px. minimum 1. | | `parts` | `object[]` | yes | When the file is too big for one readable picture, the diagrams it was split into, in order, at most 24 a page; `source` is the first diagram of all. Show or draw each of them. Empty when the import is one diagram. | | `parts[].title` | `string` | yes | What this diagram shows, such as one resource group or one tag of an API. | | `parts[].kind` | `string` | yes | The kind of diagram. | | `parts[].source` | `string` | yes | The Plotwork source of this diagram; draw it with draw_diagram. | | `parts[].items` | `integer` | yes | The boxes, rows, or entries it draws. | | `parts[].valid` | `boolean` | yes | True when the source has no Plotwork errors. | | `parts[].warnings` | `Diagnostic[]` | yes | Plotwork quality warnings of this diagram. Lines and columns point into its source. | | `parts[].warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `parts[].warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `parts[].warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `parts[].warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `parts[].warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `parts[].warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `parts[].warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `partsTotal` | `integer` | yes | How many diagrams the import was split into in all; 0 when it is one diagram. | | `partsPage` | `integer` | yes | The page of parts this answer holds, from 1. | | `partsPages` | `integer` | yes | How many pages of parts there are; call again with partsPage for the next one. | | `outline` | `string[]` | yes | The diagram as plain text, to check it says what the file says; empty when nothing was imported. | | `outlineTruncated` | `boolean` | yes | True when the outline was cut short at its summary length. | | `viewUrl` | `string` | no | A link that shows a valid diagram in a browser; give it to the person who wants to look. The source is in the part after #, never sent to a server, but anyone with the link can read it. Absent when too long; next says so. | | `pngUrl` | `string` | no | The viewUrl with &download=png: opening it in a browser saves the diagram as a PNG for Slack, email, or a wiki. The PNG is drawn in the person's browser, not on a server, so it is a link to open, not an image address to embed. Absent when too long. | | `svgUrl` | `string` | no | The viewUrl with &download=svg: a person opens it to download the SVG. Not a file address for curl; use saveCommand. Absent when too long. | | `saveCommand` | `string` | no | Run in a shell to save the SVG into the repository: the CLI decodes the link locally, fetches nothing, and embeds the source. Set --out to the path; add --theme dark for dark. Absent when too long. | | `next` | `string` | yes | The next step in one sentence. | ## ImportSourceInput A file of another format to turn into Plotwork source. | Field | Type | Required | Description | |---|---|---|---| | `format` | `"sql" \| "prisma" \| "drizzle" \| "openapi" \| "terraform" \| "terraform-plan" \| "bicep" \| "arm" \| "pulumi" \| "cloudformation" \| "cdk" \| "compose" \| "kubernetes" \| "helm" \| "kustomize" \| "backstage" \| "structurizr" \| "asyncapi" \| "github-actions" \| "gitlab-ci" \| "azure-pipelines" \| "csv" \| "json" \| "tree" \| "git-log"` | yes | What the text is; get_diagram_syntax with topic import-formats describes each. | | `text` | `string` | no | The file content, as text. Send text or files, not both. | | `files` | `object[]` | no | The files of a folder, for a format that spans files; the others read the first. | | `files[].path` | `string` | yes | The file path, such as infra/network.tf; diagnostics name it. at least 1 character; at most 300 characters. | | `files[].text` | `string` | yes | The file content, as text. | | `title` | `string` | no | The diagram title; taken from the file when left out. at most 200 characters. | | `partsPage` | `integer` | no | Which page of parts to return when a split import has more than 24 diagrams: 1 (default) is the first 24. minimum 1; maximum 100000. | | `options` | `object` | no | default {}. | | `options.view` | `"endpoints" \| "map" \| "sequence" \| "git" \| "timeline" \| "context" \| "container" \| "component"` | no | The picture of a format drawn several ways. | | `options.operationId` | `string` | no | openapi sequence: the operation. at most 200 characters. | | `options.chart` | `"auto" \| "xychart" \| "pie" \| "sankey" \| "treemap" \| "matrix"` | no | csv, json: the chart; auto picks. | | `options.tables` | `string[]` | no | Tables to draw, by name or prefix*. | | `options.paths` | `string[]` | no | openapi: path prefixes to read. | | `options.tags` | `string[]` | no | openapi: tags to read. | | `options.varFiles` | `string[]` | no | Variables files to apply. | | `options.groupBy` | `"module" \| "network" \| "file" \| "owner"` | no | How things are framed; module is the default. | | `options.root` | `string` | no | The root, stack, or template of several to draw. at most 300 characters. | ## SyncMarkdownResponse The result of turning a document's diagram fences into pictures. | Field | Type | Required | Description | |---|---|---|---| | `mode` | `"sync" \| "check"` | yes | The mode that ran. | | `markdown` | `string` | yes | The document to write back: every diagram fence wrapped in its collapsed source section, with the picture block and text description under it, and everything else unchanged. In check mode this is the document that was sent. | | `changed` | `boolean` | yes | True when the document or a file differs from what was sent. In check mode this says the pictures are stale or a fence would convert. | | `files` | `object[]` | yes | The SVG files to write, light and dark for each picture that was refreshed, with paths relative to the repository root. Empty in check mode. A picture that was already current has no files. | | `files[].path` | `string` | yes | Where to write the file, relative to the repository root. | | `files[].content` | `string` | no | The file contents; absent when files is none. | | `files[].encoding` | `"utf8"` | yes | How content is encoded. Always utf8: the files are SVG text. | | `files[].bytes` | `integer` | no | Only when files is none: the size of the file in bytes. | | `files[].sha256` | `string` | no | Only when files is none: the SHA-256 of the content, in hex, to check a file fetched later. | | `files[].viewUrl` | `string` | no | Only when files is none: a link that shows this diagram in a browser. | | `report` | `object[]` | yes | One entry per fence handled, in document order. | | `report[].id` | `string \| null` | yes | The picture id, which names the SVG files; null for a fence that has none (a mermaid fence that was not converted). | | `report[].source` | `"diagram" \| "mermaid"` | yes | diagram for a fence that was already Plotwork, mermaid for one that came from Mermaid. | | `report[].kind` | `string \| null` | yes | The diagram kind, or null when it could not be read. | | `report[].line` | `integer` | yes | The 1-based line of the opening fence in the document that was sent. minimum 1. | | `report[].status` | `"synced" \| "converted" \| "unchanged" \| "skipped" \| "failed"` | yes | synced: the picture was written or refreshed. converted: a mermaid fence became a diagram fence and was drawn. unchanged: the picture was already current. skipped: a cap stopped it, see message. failed: it has errors, the fence is left as it was. | | `report[].diagnostics` | `Diagnostic[]` | yes | Errors and warnings, with line numbers counted in the document that was sent. For a converted fence they point at the Mermaid lines. | | `report[].diagnostics[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `report[].diagnostics[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `report[].diagnostics[].message` | `string` | yes | What is wrong, in one sentence. | | `report[].diagnostics[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `report[].diagnostics[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `report[].diagnostics[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `report[].diagnostics[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `report[].message` | `string \| null` | yes | A plain sentence when something needs saying, such as why a fence was skipped; otherwise null. | | `summary` | `object` | yes | | | `summary.diagrams` | `integer` | yes | Fences handled: synced, converted, unchanged, skipped, and failed together. | | `summary.synced` | `integer` | yes | | | `summary.converted` | `integer` | yes | | | `summary.unchanged` | `integer` | yes | | | `summary.skipped` | `integer` | yes | | | `summary.failed` | `integer` | yes | | | `summary.mermaidIgnored` | `integer` | yes | Mermaid fences left alone because convertMermaid was false. | | `summary.files` | `integer` | yes | Files to write. | | `summary.outputBytes` | `integer` | yes | Bytes in the returned document and files together. | | `caps` | `object` | yes | | | `caps.hit` | `boolean` | yes | True when a cap stopped some of the work. Nothing is cut silently: see messages. | | `caps.messages` | `string[]` | yes | One plain sentence per cap that was hit, saying what was skipped and what to do. | | `caps.maxDiagrams` | `integer` | yes | Most fences handled in one call. | | `caps.maxOutputBytes` | `integer` | yes | Most bytes in the returned document and files together. | | `pages` | `object` | no | Present when page or pageSize was sent: which pictures files holds, and the next page. | | `pages.page` | `integer` | yes | | | `pages.pageSize` | `integer` | yes | | | `pages.pages` | `integer` | yes | Pages in all. | | `pages.pictures` | `integer` | yes | Pictures in all, each a light and a dark file. | | `pages.nextPage` | `integer \| null` | yes | The page to ask for next, or null on the last one. | | `next` | `string` | yes | The next step in one sentence. | ## SyncMarkdownInput A Markdown document and how to turn its diagram fences into pictures. | Field | Type | Required | Description | |---|---|---|---| | `markdown` | `string` | yes | The whole Markdown document, exactly as it is on disk, with its line endings. Up to 10000000 characters. at least 1 character; at most 10000000 characters. | | `documentPath` | `string` | yes | Where the document lives, relative to the repository root, with forward slashes, for example docs/guide.md. Picture links and file paths are worked out from it. at least 1 character; at most 300 characters. | | `assetsDir` | `string` | no | The directory for the SVG files, relative to the repository root. The default is assets/diagrams next to the document. at least 1 character; at most 300 characters. | | `prefix` | `string` | no | Text put in front of every picture file name, for documents that share one assets directory. By default a titled diagram is named from its title and an untitled one from the document name. at least 1 character; at most 32 characters. | | `convertMermaid` | `boolean` | no | When true, every ```mermaid fence that can be converted is replaced by a ```diagram fence first, then drawn. Fences that cannot be converted are left exactly as they were and reported. default false. | | `mode` | `"sync" \| "check"` | no | sync (the default) returns the rewritten document and the files to write. check changes nothing: it only reports whether a picture is stale or a fence would convert. default "sync". | | `files` | `"inline" \| "none"` | no | inline (the default) returns each SVG file with its content. none returns each with path, bytes, sha256, and viewUrl instead, so the answer stays small. default "inline". | | `page` | `integer` | no | Which page of files to return, from 1; pictures are paged in document order, light and dark together. minimum 1. | | `pageSize` | `integer` | no | Pictures per page, at most 30. Setting page or pageSize turns paging on. minimum 1; maximum 30. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## PlanDocumentResponse The sections of a document a picture would help, each with a kind, a reason, and a starter. | Field | Type | Required | Description | |---|---|---|---| | `summary` | `string` | yes | One sentence: how many sections were read and how many would gain a picture. | | `format` | `"markdown" \| "html" \| "text"` | yes | How the document was read: Markdown headings, HTML h1 to h6, or plain text with heading-like lines. | | `sectionsRead` | `integer` | yes | Sections read, up to the cap. | | `suggestions` | `object[]` | yes | In document order. Sections that read well as text, or already have a diagram, are not listed. | | `suggestions[].heading` | `string` | yes | The heading of the section, as written. | | `suggestions[].line` | `integer` | yes | The 1-based line of the heading in the document that was sent. minimum 1. | | `suggestions[].kind` | `string` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `suggestions[].style` | `string \| null` | yes | The style the starter uses: compare, score, raci, or risk for a matrix, files or org for a tree; null for the kind's only style. | | `suggestions[].why` | `string` | yes | One sentence: what in the section points to this kind. | | `suggestions[].confidence` | `"high" \| "medium" \| "low"` | yes | high: the text names this kind of picture or its shape fits it exactly. medium: it leans toward it. low: a fallback worth considering. | | `suggestions[].starter` | `string` | yes | A short Plotwork source of this kind that parses without errors: built from the names and numbers in the text where it could find them, otherwise a small example to edit. Replace every placeholder before you draw it. | | `suggestions[].tailored` | `boolean` | yes | True when the starter uses names or numbers from the text; false when it is the kind's small example. | | `caps` | `object` | yes | | | `caps.hit` | `boolean` | yes | True when a cap left something out; messages says what. | | `caps.messages` | `string[]` | yes | | | `caps.maxSections` | `integer` | yes | | | `caps.maxSuggestions` | `integer` | yes | | | `next` | `string` | yes | The next step in one sentence. | ## PlanDocumentInput A document to read for the sections a picture would help. | Field | Type | Required | Description | |---|---|---|---| | `document` | `string` | yes | The whole document: Markdown, plain text pasted from Confluence, Notion, or Google Docs, or HTML. Up to 10000000 characters. at least 1 character; at most 10000000 characters. | | `maxSuggestions` | `integer` | no | Most sections to suggest a picture for, up to 12. The strongest are kept, in document order. The default is 8. minimum 1; maximum 12; default 8. | ## OrganisationList | Field | Type | Required | Description | |---|---|---|---| | `items` | `Organisation[]` | yes | By name, at most 100. | | `items[].id` | `string` | yes | | | `items[].slug` | `string` | yes | | | `items[].name` | `string` | yes | | | `items[].role` | `"owner" \| "admin" \| "member" \| "reader"` | yes | What the caller is in this organisation. | | `items[].createdAt` | `integer` | yes | Milliseconds since the epoch. | | `items[].memberCount` | `integer` | yes | People who have joined, the owner included. The same number as `usage.members`. minimum 0. | | `items[].plan` | `"free" \| "pro" \| "enterprise"` | yes | The organisation's plan: `enterprise` under a contract, otherwise its owner's. | | `items[].contractEndsOn` | `string \| null` | yes | The last day of the Enterprise contract, `YYYY-MM-DD`, or null when there is none or it has no end date written. The terms of a contract are not shown to members. | | `items[].usage` | `OrganisationUsage` | yes | | | `items[].usage.diagrams` | `integer` | yes | minimum 0. | | `items[].usage.bytes` | `integer` | yes | minimum 0. | | `items[].usage.members` | `integer` | yes | People who have joined, the owner included. minimum 0. | | `items[].usage.invites` | `integer` | yes | Invitations that can still be accepted. They count toward the member limit. minimum 0. | | `items[].limits` | `OrganisationLimits` | yes | | | `items[].limits.dailyCalls` | `integer` | yes | | | `items[].limits.hostedDiagrams` | `integer` | yes | Diagrams the organisation may hold. | | `items[].limits.hostedBytes` | `integer` | yes | Total bytes of diagrams the organisation may hold. | | `items[].limits.retentionDays` | `integer \| null` | yes | Days a diagram is kept after it was last published, or null to keep it until it is deleted. | | `items[].limits.maxDiagramBytes` | `integer` | yes | | | `items[].limits.stableLinks` | `integer` | yes | Stable links the owner may keep for their own diagrams. An organisation's diagrams have none. | | `items[].limits.organisations` | `integer` | yes | Organisations an account on this plan may own. | | `items[].limits.orgMembers` | `integer` | yes | People one organisation may have, the owner included and pending invitations counted. | | `items[].limits.orgIcons` | `integer` | yes | Custom icons one organisation may keep; zero on a plan without custom icons. minimum 0. | | `items[].limits.driftHistoryDays` | `integer` | yes | Days the drift dashboard keeps a run and a status after it was reported; zero on a plan without the dashboard. minimum 0. | | `items[].limits.auditRetentionDays` | `integer` | yes | Days the organisation's audit log keeps an event; zero on a plan that records none. minimum 0. | | `items[].avatar` | `OrgAvatar` | yes | The organisation's uploaded avatar, or null when it has none and a mark made from its id stands in. | | `items[].avatar.version` | `string` | yes | Changes with every upload. Put it in the address (?v=) so a new picture is a new address. | | `items[].avatar.contentType` | `"image/svg+xml" \| "image/png" \| "image/gif" \| "image/webp"` | yes | | | `items[].avatar.animated` | `boolean` | yes | True when it moves, so a page can hold it still for a person who asked for less motion. | | `items[].keepEditable` | `boolean` | yes | The owner's setting "Keep diagrams editable": true when a publish to the organisation that does not send embedSource writes the diagram source into the picture, so members can edit it in the portal later. Every member can open such a picture and read its source. | | `items[].publishing` | `OrganisationPublishing` | yes | | | `items[].publishing.policy` | `"personal" \| "organisation-default" \| "organisation-required"` | yes | What the owner or an admin chose: personal (members publish where they choose), organisation-default (a member's publish that names no organisation goes to this library), or organisation-required (nothing a member publishes reaches a public link). | | `items[].publishing.effective` | `"personal" \| "organisation-default" \| "organisation-required"` | yes | What is enforced now: the choice, except organisation-required on a plan that no longer includes it, which acts as organisation-default. | | `items[].publishing.hideViewLinks` | `boolean` | yes | Whether hosted answers leave view links out for members and the organisation's keys. Honoured only while effective is organisation-required. | | `items[].publishing.requiredAvailable` | `boolean` | yes | Whether the organisation's plan includes organisation-required (Pro and Enterprise). | ## Organisation | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `slug` | `string` | yes | | | `name` | `string` | yes | | | `role` | `"owner" \| "admin" \| "member" \| "reader"` | yes | What the caller is in this organisation. | | `createdAt` | `integer` | yes | Milliseconds since the epoch. | | `memberCount` | `integer` | yes | People who have joined, the owner included. The same number as `usage.members`. minimum 0. | | `plan` | `"free" \| "pro" \| "enterprise"` | yes | The organisation's plan: `enterprise` under a contract, otherwise its owner's. | | `contractEndsOn` | `string \| null` | yes | The last day of the Enterprise contract, `YYYY-MM-DD`, or null when there is none or it has no end date written. The terms of a contract are not shown to members. | | `usage` | `OrganisationUsage` | yes | | | `usage.diagrams` | `integer` | yes | minimum 0. | | `usage.bytes` | `integer` | yes | minimum 0. | | `usage.members` | `integer` | yes | People who have joined, the owner included. minimum 0. | | `usage.invites` | `integer` | yes | Invitations that can still be accepted. They count toward the member limit. minimum 0. | | `limits` | `OrganisationLimits` | yes | | | `limits.dailyCalls` | `integer` | yes | | | `limits.hostedDiagrams` | `integer` | yes | Diagrams the organisation may hold. | | `limits.hostedBytes` | `integer` | yes | Total bytes of diagrams the organisation may hold. | | `limits.retentionDays` | `integer \| null` | yes | Days a diagram is kept after it was last published, or null to keep it until it is deleted. | | `limits.maxDiagramBytes` | `integer` | yes | | | `limits.stableLinks` | `integer` | yes | Stable links the owner may keep for their own diagrams. An organisation's diagrams have none. | | `limits.organisations` | `integer` | yes | Organisations an account on this plan may own. | | `limits.orgMembers` | `integer` | yes | People one organisation may have, the owner included and pending invitations counted. | | `limits.orgIcons` | `integer` | yes | Custom icons one organisation may keep; zero on a plan without custom icons. minimum 0. | | `limits.driftHistoryDays` | `integer` | yes | Days the drift dashboard keeps a run and a status after it was reported; zero on a plan without the dashboard. minimum 0. | | `limits.auditRetentionDays` | `integer` | yes | Days the organisation's audit log keeps an event; zero on a plan that records none. minimum 0. | | `avatar` | `OrgAvatar` | yes | The organisation's uploaded avatar, or null when it has none and a mark made from its id stands in. | | `avatar.version` | `string` | yes | Changes with every upload. Put it in the address (?v=) so a new picture is a new address. | | `avatar.contentType` | `"image/svg+xml" \| "image/png" \| "image/gif" \| "image/webp"` | yes | | | `avatar.animated` | `boolean` | yes | True when it moves, so a page can hold it still for a person who asked for less motion. | | `keepEditable` | `boolean` | yes | The owner's setting "Keep diagrams editable": true when a publish to the organisation that does not send embedSource writes the diagram source into the picture, so members can edit it in the portal later. Every member can open such a picture and read its source. | | `publishing` | `OrganisationPublishing` | yes | | | `publishing.policy` | `"personal" \| "organisation-default" \| "organisation-required"` | yes | What the owner or an admin chose: personal (members publish where they choose), organisation-default (a member's publish that names no organisation goes to this library), or organisation-required (nothing a member publishes reaches a public link). | | `publishing.effective` | `"personal" \| "organisation-default" \| "organisation-required"` | yes | What is enforced now: the choice, except organisation-required on a plan that no longer includes it, which acts as organisation-default. | | `publishing.hideViewLinks` | `boolean` | yes | Whether hosted answers leave view links out for members and the organisation's keys. Honoured only while effective is organisation-required. | | `publishing.requiredAvailable` | `boolean` | yes | Whether the organisation's plan includes organisation-required (Pro and Enterprise). | ## OrganisationUsage | Field | Type | Required | Description | |---|---|---|---| | `diagrams` | `integer` | yes | minimum 0. | | `bytes` | `integer` | yes | minimum 0. | | `members` | `integer` | yes | People who have joined, the owner included. minimum 0. | | `invites` | `integer` | yes | Invitations that can still be accepted. They count toward the member limit. minimum 0. | ## OrganisationLimits | Field | Type | Required | Description | |---|---|---|---| | `dailyCalls` | `integer` | yes | | | `hostedDiagrams` | `integer` | yes | Diagrams the organisation may hold. | | `hostedBytes` | `integer` | yes | Total bytes of diagrams the organisation may hold. | | `retentionDays` | `integer \| null` | yes | Days a diagram is kept after it was last published, or null to keep it until it is deleted. | | `maxDiagramBytes` | `integer` | yes | | | `stableLinks` | `integer` | yes | Stable links the owner may keep for their own diagrams. An organisation's diagrams have none. | | `organisations` | `integer` | yes | Organisations an account on this plan may own. | | `orgMembers` | `integer` | yes | People one organisation may have, the owner included and pending invitations counted. | | `orgIcons` | `integer` | yes | Custom icons one organisation may keep; zero on a plan without custom icons. minimum 0. | | `driftHistoryDays` | `integer` | yes | Days the drift dashboard keeps a run and a status after it was reported; zero on a plan without the dashboard. minimum 0. | | `auditRetentionDays` | `integer` | yes | Days the organisation's audit log keeps an event; zero on a plan that records none. minimum 0. | ## OrgAvatar The organisation's uploaded avatar, or null when it has none and a mark made from its id stands in. | Field | Type | Required | Description | |---|---|---|---| | `version` | `string` | yes | Changes with every upload. Put it in the address (?v=) so a new picture is a new address. | | `contentType` | `"image/svg+xml" \| "image/png" \| "image/gif" \| "image/webp"` | yes | | | `animated` | `boolean` | yes | True when it moves, so a page can hold it still for a person who asked for less motion. | ## OrganisationPublishing | Field | Type | Required | Description | |---|---|---|---| | `policy` | `"personal" \| "organisation-default" \| "organisation-required"` | yes | What the owner or an admin chose: personal (members publish where they choose), organisation-default (a member's publish that names no organisation goes to this library), or organisation-required (nothing a member publishes reaches a public link). | | `effective` | `"personal" \| "organisation-default" \| "organisation-required"` | yes | What is enforced now: the choice, except organisation-required on a plan that no longer includes it, which acts as organisation-default. | | `hideViewLinks` | `boolean` | yes | Whether hosted answers leave view links out for members and the organisation's keys. Honoured only while effective is organisation-required. | | `requiredAvailable` | `boolean` | yes | Whether the organisation's plan includes organisation-required (Pro and Enterprise). | ## CreateOrganisationBody | Field | Type | Required | Description | |---|---|---|---| | `name` | `string` | yes | What people read. 1 to 60 characters. It can be changed; the slug cannot. at least 1 character; pattern `^[^\p{Cc}]+$/u`. | | `slug` | `string` | no | The handle. Left out, it is made from the name, with a short suffix when that is taken. at least 3 characters; at most 40 characters; pattern `^[a-z0-9][a-z0-9-]*[a-z0-9]$`. | ## RenameOrganisationBody | Field | Type | Required | Description | |---|---|---|---| | `name` | `string` | yes | What people read. 1 to 60 characters. It can be changed; the slug cannot. at least 1 character; pattern `^[^\p{Cc}]+$/u`. | ## OrganisationMemberList | Field | Type | Required | Description | |---|---|---|---| | `items` | `OrganisationMember[]` | yes | | | `items[].userId` | `string` | yes | | | `items[].email` | `string` | yes | Shown to every member: a team knows who is in it. | | `items[].role` | `"owner" \| "admin" \| "member" \| "reader"` | yes | | | `items[].joinedAt` | `integer` | yes | Milliseconds since the epoch. | ## OrganisationMember | Field | Type | Required | Description | |---|---|---|---| | `userId` | `string` | yes | | | `email` | `string` | yes | Shown to every member: a team knows who is in it. | | `role` | `"owner" \| "admin" \| "member" \| "reader"` | yes | | | `joinedAt` | `integer` | yes | Milliseconds since the epoch. | ## ChangeMemberRoleBody | Field | Type | Required | Description | |---|---|---|---| | `role` | `"admin" \| "member" \| "reader"` | yes | `admin`, `member` or `reader`. An admin can invite and remove members and readers, rename the organisation and manage its keys; a member can publish and change their own diagrams; a reader can view and download and change nothing. Only the owner offers `admin` or changes a role. | ## OrganisationInviteList | Field | Type | Required | Description | |---|---|---|---| | `items` | `OrganisationInvite[]` | yes | | | `items[].id` | `string` | yes | | | `items[].email` | `string` | yes | | | `items[].role` | `"admin" \| "member" \| "reader"` | yes | | | `items[].createdAt` | `integer` | yes | Milliseconds since the epoch. | | `items[].expiresAt` | `integer` | yes | Milliseconds since the epoch. Seven days after it was made. | ## OrganisationInvite | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `email` | `string` | yes | | | `role` | `"admin" \| "member" \| "reader"` | yes | | | `createdAt` | `integer` | yes | Milliseconds since the epoch. | | `expiresAt` | `integer` | yes | Milliseconds since the epoch. Seven days after it was made. | ## CreateInviteBody | Field | Type | Required | Description | |---|---|---|---| | `email` | `string` | yes | The address to invite. The person must sign in with this address to accept. at least 3 characters; at most 254 characters. | | `role` | `"admin" \| "member" \| "reader"` | yes | `admin`, `member` or `reader`. An admin can invite and remove members and readers, rename the organisation and manage its keys; a member can publish and change their own diagrams; a reader can view and download and change nothing. Only the owner offers `admin` or changes a role. | ## AcceptInviteBody | Field | Type | Required | Description | |---|---|---|---| | `token` | `string` | yes | The token in the invitation link. at least 1 character; at most 200 characters. | ## OrganisationDiagramList | Field | Type | Required | Description | |---|---|---|---| | `items` | `OrganisationDiagram[]` | yes | At most 200, newest published first. | | `items[].id` | `string` | yes | The handle for the diagram in the routes below. | | `items[].name` | `string \| null` | yes | The label given at publish, or null. | | `items[].folder` | `string \| null` | yes | The folder it is filed in, such as `Payments/Checkout`, or null for the top level. | | `items[].theme` | `"light" \| "dark" \| "auto"` | yes | The color scheme it was drawn in: light, dark, or auto. | | `items[].bytes` | `integer` | yes | Size of the stored SVG in bytes. | | `items[].createdBy` | `string \| null` | yes | The account that published it, or null when that account has left or an organisation key published it. | | `items[].createdAt` | `integer` | yes | When it was first published, in milliseconds since the epoch. | | `items[].publishedAt` | `integer` | yes | When it was last published, in milliseconds since the epoch. Publishing the same diagram again moves it. | | `items[].expiresAt` | `integer \| null` | yes | When it will be deleted, in milliseconds since the epoch, or null when the owner's plan keeps diagrams until they are deleted. | | `items[].viewUrl` | `string` | yes | A page in the portal that shows the diagram to a signed-in member. It is not a link anyone can open: an organisation's diagrams have no public address. | | `items[].locked` | `boolean` | yes | True while the owner has the diagram locked: nobody can delete, rename or move it, its publisher and the owner included, until the owner unlocks it. A lock does not stop it expiring on a plan that expires diagrams. | | `items[].lockedAt` | `integer \| null` | yes | When the owner locked it, in milliseconds since the epoch, or null when it is not locked. | | `items[].restricted` | `true` | no | Present, and true, when the owner has restricted this diagram's folder and the caller is allowed in it, so the portal can show it as locked. Absent otherwise. A diagram in a folder the caller may not use is not listed at all. | | `usage` | `OrganisationHostingUsage` | yes | | | `usage.plan` | `"free" \| "pro" \| "enterprise"` | yes | | | `usage.count` | `integer` | yes | Diagrams the organisation holds. This is the total, even when the list is shorter. minimum 0. | | `usage.bytes` | `integer` | yes | minimum 0. | | `usage.limits` | `OrganisationLimits` | yes | | | `usage.limits.dailyCalls` | `integer` | yes | | | `usage.limits.hostedDiagrams` | `integer` | yes | Diagrams the organisation may hold. | | `usage.limits.hostedBytes` | `integer` | yes | Total bytes of diagrams the organisation may hold. | | `usage.limits.retentionDays` | `integer \| null` | yes | Days a diagram is kept after it was last published, or null to keep it until it is deleted. | | `usage.limits.maxDiagramBytes` | `integer` | yes | | | `usage.limits.stableLinks` | `integer` | yes | Stable links the owner may keep for their own diagrams. An organisation's diagrams have none. | | `usage.limits.organisations` | `integer` | yes | Organisations an account on this plan may own. | | `usage.limits.orgMembers` | `integer` | yes | People one organisation may have, the owner included and pending invitations counted. | | `usage.limits.orgIcons` | `integer` | yes | Custom icons one organisation may keep; zero on a plan without custom icons. minimum 0. | | `usage.limits.driftHistoryDays` | `integer` | yes | Days the drift dashboard keeps a run and a status after it was reported; zero on a plan without the dashboard. minimum 0. | | `usage.limits.auditRetentionDays` | `integer` | yes | Days the organisation's audit log keeps an event; zero on a plan that records none. minimum 0. | ## OrganisationHostingUsage | Field | Type | Required | Description | |---|---|---|---| | `plan` | `"free" \| "pro" \| "enterprise"` | yes | | | `count` | `integer` | yes | Diagrams the organisation holds. This is the total, even when the list is shorter. minimum 0. | | `bytes` | `integer` | yes | minimum 0. | | `limits` | `OrganisationLimits` | yes | | | `limits.dailyCalls` | `integer` | yes | | | `limits.hostedDiagrams` | `integer` | yes | Diagrams the organisation may hold. | | `limits.hostedBytes` | `integer` | yes | Total bytes of diagrams the organisation may hold. | | `limits.retentionDays` | `integer \| null` | yes | Days a diagram is kept after it was last published, or null to keep it until it is deleted. | | `limits.maxDiagramBytes` | `integer` | yes | | | `limits.stableLinks` | `integer` | yes | Stable links the owner may keep for their own diagrams. An organisation's diagrams have none. | | `limits.organisations` | `integer` | yes | Organisations an account on this plan may own. | | `limits.orgMembers` | `integer` | yes | People one organisation may have, the owner included and pending invitations counted. | | `limits.orgIcons` | `integer` | yes | Custom icons one organisation may keep; zero on a plan without custom icons. minimum 0. | | `limits.driftHistoryDays` | `integer` | yes | Days the drift dashboard keeps a run and a status after it was reported; zero on a plan without the dashboard. minimum 0. | | `limits.auditRetentionDays` | `integer` | yes | Days the organisation's audit log keeps an event; zero on a plan that records none. minimum 0. | ## PublishOrganisationDiagramBody | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | Diagram source; the first line is the header, such as flow "Title" (draw_diagram lists every kind). at least 1 character; at most 50000 characters. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | | `theme` | `"light" \| "dark" \| "auto"` | no | The color scheme to draw it in: light, dark, or auto (one picture that follows the viewer's light or dark setting). default "light". | | `embedSource` | `boolean` | no | Write the diagram source into the stored SVG, in its metadata, so the diagram can be edited later; every member who can open it can then read the source. The same diagram published with and without it is two pictures. Left out, the organisation's keepEditable setting decides (false unless the owner turned it on); false always leaves the source out. | | `name` | `string` | no | A label for the diagram in your list and in the Markdown snippet. Optional. at least 1 character; at most 80 characters; pattern `^[^\p{Cc}]+$/u`. | | `folder` | `string \| null` | no | The folder to file the diagram in. Left out, a new diagram goes to the top level and a diagram that is already hosted stays where it is. A path files a new diagram there, or moves the one that is already hosted and keeps its link. null puts it at the top level. | ## UpdateOrganisationDiagramBody | Field | Type | Required | Description | |---|---|---|---| | `folder` | `string \| null` | no | The folder to file the diagram in, or null for the top level. | | `name` | `string \| null` | no | A new label, or null to clear it. at least 1 character; at most 80 characters; pattern `^[^\p{Cc}]+$/u`. | ## EditedOrganisationDiagram | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The handle for the diagram in the routes below. | | `name` | `string \| null` | yes | The label given at publish, or null. | | `folder` | `string \| null` | yes | The folder it is filed in, such as `Payments/Checkout`, or null for the top level. | | `theme` | `"light" \| "dark" \| "auto"` | yes | The color scheme it was drawn in: light, dark, or auto. | | `bytes` | `integer` | yes | Size of the stored SVG in bytes. | | `createdBy` | `string \| null` | yes | The account that published it, or null when that account has left or an organisation key published it. | | `createdAt` | `integer` | yes | When it was first published, in milliseconds since the epoch. | | `publishedAt` | `integer` | yes | When it was last published, in milliseconds since the epoch. Publishing the same diagram again moves it. | | `expiresAt` | `integer \| null` | yes | When it will be deleted, in milliseconds since the epoch, or null when the owner's plan keeps diagrams until they are deleted. | | `viewUrl` | `string` | yes | A page in the portal that shows the diagram to a signed-in member. It is not a link anyone can open: an organisation's diagrams have no public address. | | `locked` | `boolean` | yes | True while the owner has the diagram locked: nobody can delete, rename or move it, its publisher and the owner included, until the owner unlocks it. A lock does not stop it expiring on a plan that expires diagrams. | | `lockedAt` | `integer \| null` | yes | When the owner locked it, in milliseconds since the epoch, or null when it is not locked. | | `restricted` | `true` | no | Present, and true, when the owner has restricted this diagram's folder and the caller is allowed in it, so the portal can show it as locked. Absent otherwise. A diagram in a folder the caller may not use is not listed at all. | | `warnings` | `Diagnostic[]` | yes | Quality warnings from drawing the source, as draw_diagram gives them. Empty for a restore. | | `warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `changed` | `boolean` | yes | False when the source draws the picture the diagram already has: nothing was stored and no version was kept. | ## KeepEditable | Field | Type | Required | Description | |---|---|---|---| | `keepEditable` | `boolean` | yes | True to write the diagram source into every picture published to the organisation that does not say otherwise, so members can edit it in the portal later. A publish that sends embedSource false still leaves it out. | ## SetOrganisationPublishingBody | Field | Type | Required | Description | |---|---|---|---| | `policy` | `"personal" \| "organisation-default" \| "organisation-required"` | yes | | | `hideViewLinks` | `boolean` | no | Leave view links out of hosted answers for members and keys. Only with organisation-required. default false. | ## MemberPublicDiagramList | Field | Type | Required | Description | |---|---|---|---| | `items` | `MemberPublicDiagram[]` | yes | At most 200, newest published first. | | `items[].id` | `string` | yes | The personal diagram's id. | | `items[].name` | `string \| null` | yes | | | `items[].url` | `string` | yes | Its public link, which anyone who has it can open. | | `items[].createdAt` | `integer` | yes | | | `items[].publishedAt` | `integer` | yes | | | `items[].bytes` | `integer` | yes | | | `items[].locked` | `boolean` | yes | Whether the member locked it. Moving and unpublishing override the member's lock. | | `items[].stableLinks` | `integer` | yes | How many of the member's stable links show it now. minimum 0. | | `items[].member` | `object` | yes | | | `items[].member.userId` | `string` | yes | | | `items[].member.email` | `string` | yes | | | `total` | `integer` | yes | How many there are, which may be more than the list holds. minimum 0. | ## MemberPublicDiagram | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The personal diagram's id. | | `name` | `string \| null` | yes | | | `url` | `string` | yes | Its public link, which anyone who has it can open. | | `createdAt` | `integer` | yes | | | `publishedAt` | `integer` | yes | | | `bytes` | `integer` | yes | | | `locked` | `boolean` | yes | Whether the member locked it. Moving and unpublishing override the member's lock. | | `stableLinks` | `integer` | yes | How many of the member's stable links show it now. minimum 0. | | `member` | `object` | yes | | | `member.userId` | `string` | yes | | | `member.email` | `string` | yes | | ## MovedMemberDiagram | Field | Type | Required | Description | |---|---|---|---| | `diagram` | `OrganisationDiagram` | yes | | | `diagram.id` | `string` | yes | The handle for the diagram in the routes below. | | `diagram.name` | `string \| null` | yes | The label given at publish, or null. | | `diagram.folder` | `string \| null` | yes | The folder it is filed in, such as `Payments/Checkout`, or null for the top level. | | `diagram.theme` | `"light" \| "dark" \| "auto"` | yes | The color scheme it was drawn in: light, dark, or auto. | | `diagram.bytes` | `integer` | yes | Size of the stored SVG in bytes. | | `diagram.createdBy` | `string \| null` | yes | The account that published it, or null when that account has left or an organisation key published it. | | `diagram.createdAt` | `integer` | yes | When it was first published, in milliseconds since the epoch. | | `diagram.publishedAt` | `integer` | yes | When it was last published, in milliseconds since the epoch. Publishing the same diagram again moves it. | | `diagram.expiresAt` | `integer \| null` | yes | When it will be deleted, in milliseconds since the epoch, or null when the owner's plan keeps diagrams until they are deleted. | | `diagram.viewUrl` | `string` | yes | A page in the portal that shows the diagram to a signed-in member. It is not a link anyone can open: an organisation's diagrams have no public address. | | `diagram.locked` | `boolean` | yes | True while the owner has the diagram locked: nobody can delete, rename or move it, its publisher and the owner included, until the owner unlocks it. A lock does not stop it expiring on a plan that expires diagrams. | | `diagram.lockedAt` | `integer \| null` | yes | When the owner locked it, in milliseconds since the epoch, or null when it is not locked. | | `diagram.restricted` | `true` | no | Present, and true, when the owner has restricted this diagram's folder and the caller is allowed in it, so the portal can show it as locked. Absent otherwise. A diagram in a folder the caller may not use is not listed at all. | | `merged` | `boolean` | yes | True when the library already held exactly this picture, so only the public copy went. | ## UserPublishing | Field | Type | Required | Description | |---|---|---|---| | `defaultOrganization` | `any` | yes | | | `defaultOrganization.id` | `string` | yes | | | `defaultOrganization.slug` | `string` | yes | | | `defaultOrganization.name` | `string` | yes | | | `organizations` | `PublishingClaim[]` | yes | The organisations whose Publishing setting claims the person's publishes, by name. | | `organizations[].id` | `string` | yes | | | `organizations[].slug` | `string` | yes | | | `organizations[].name` | `string` | yes | | | `organizations[].policy` | `"organisation-default" \| "organisation-required"` | yes | What it enforces: its library by default, or its library always. | | `organizations[].via` | `"member" \| "domain"` | yes | member when the person belongs to it; domain when it verified the domain of their address and they have not joined. | | `organizations[].canPublish` | `boolean` | yes | Whether the person may publish to its library: a member who is not a reader. | | `target` | `PublishTarget` | yes | personal, the organisation it goes to and why, choose (several claim it and none is the default), or refused. | ## PublishingClaim | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `slug` | `string` | yes | | | `name` | `string` | yes | | | `policy` | `"organisation-default" \| "organisation-required"` | yes | What it enforces: its library by default, or its library always. | | `via` | `"member" \| "domain"` | yes | member when the person belongs to it; domain when it verified the domain of their address and they have not joined. | | `canPublish` | `boolean` | yes | Whether the person may publish to its library: a member who is not a reader. | ## PublishTarget personal, the organisation it goes to and why, choose (several claim it and none is the default), or refused. | Field | Type | Required | Description | |---|---|---|---| ## SetUserPublishingBody | Field | Type | Required | Description | |---|---|---|---| | `defaultOrganization` | `string \| null` | yes | An organisation's slug or id, or null for none. The person must be a member who may publish there. at least 1 character; at most 64 characters. | ## FolderRuleList | Field | Type | Required | Description | |---|---|---|---| | `items` | `FolderRule[]` | yes | By folder, at most 50. | | `items[].folder` | `string` | yes | The restricted folder, in the form diagrams carry in their `folder`. | | `items[].roles` | `("admin" \| "member" \| "reader" \| "key")[]` | yes | The roles let in, apart from the owner, who is always allowed: `admin`, `member`, `reader`, and `key` for the organisation's own API keys. | | `items[].users` | `FolderRuleUser[]` | yes | The people let in by name, whatever their role. | | `items[].users[].userId` | `string` | yes | The member's account id. | | `items[].users[].email` | `string` | yes | The member's address, as the member list shows it. | | `items[].createdAt` | `integer` | yes | Milliseconds since the epoch. When the rule as it now stands was set; replacing a rule sets it again. | ## FolderRule | Field | Type | Required | Description | |---|---|---|---| | `folder` | `string` | yes | The restricted folder, in the form diagrams carry in their `folder`. | | `roles` | `("admin" \| "member" \| "reader" \| "key")[]` | yes | The roles let in, apart from the owner, who is always allowed: `admin`, `member`, `reader`, and `key` for the organisation's own API keys. | | `users` | `FolderRuleUser[]` | yes | The people let in by name, whatever their role. | | `users[].userId` | `string` | yes | The member's account id. | | `users[].email` | `string` | yes | The member's address, as the member list shows it. | | `createdAt` | `integer` | yes | Milliseconds since the epoch. When the rule as it now stands was set; replacing a rule sets it again. | ## FolderRuleUser | Field | Type | Required | Description | |---|---|---|---| | `userId` | `string` | yes | The member's account id. | | `email` | `string` | yes | The member's address, as the member list shows it. | ## SetFolderRuleBody | Field | Type | Required | Description | |---|---|---|---| | `folder` | `string` | yes | The folder the rule restricts, with every folder inside it: names from the outermost in, joined with a slash, with no slash at either end. at least 1 character; at most 405 characters. | | `roles` | `string[]` | yes | Any of admin, member, reader, key. `owner` is not a choice. An empty list, with no people, lets in the owner alone. | | `users` | `string[]` | yes | Account ids of current members, at most 100. A person who leaves is taken out of every rule. | ## OrgApiKeyList | Field | Type | Required | Description | |---|---|---|---| | `items` | `OrgApiKeySummary[]` | yes | | | `items[].id` | `string` | yes | | | `items[].name` | `string` | yes | | | `items[].prefix` | `string` | yes | | | `items[].last4` | `string` | yes | | | `items[].createdAt` | `integer` | yes | Milliseconds since the epoch. | | `items[].lastUsedAt` | `integer \| null` | yes | Milliseconds since the epoch, or null if never used. | | `items[].revealable` | `boolean` | yes | True when the full key can be shown again with the reveal route. False for a key made before that was possible, or on a server that cannot seal keys. | | `items[].scopes` | `("diagrams" \| "audit:read")[]` | yes | `diagrams`: read the library, publish, and change its diagrams, which every key made before scopes holds. `audit:read`: read and export the audit log, and nothing else unless `diagrams` is also given. | ## OrgApiKeySummary | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `name` | `string` | yes | | | `prefix` | `string` | yes | | | `last4` | `string` | yes | | | `createdAt` | `integer` | yes | Milliseconds since the epoch. | | `lastUsedAt` | `integer \| null` | yes | Milliseconds since the epoch, or null if never used. | | `revealable` | `boolean` | yes | True when the full key can be shown again with the reveal route. False for a key made before that was possible, or on a server that cannot seal keys. | | `scopes` | `("diagrams" \| "audit:read")[]` | yes | `diagrams`: read the library, publish, and change its diagrams, which every key made before scopes holds. `audit:read`: read and export the audit log, and nothing else unless `diagrams` is also given. | ## CreatedOrgApiKey | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `name` | `string` | yes | | | `prefix` | `string` | yes | | | `last4` | `string` | yes | | | `createdAt` | `integer` | yes | Milliseconds since the epoch. | | `lastUsedAt` | `integer \| null` | yes | Milliseconds since the epoch, or null if never used. | | `revealable` | `boolean` | yes | True when the full key can be shown again with the reveal route. False for a key made before that was possible, or on a server that cannot seal keys. | | `key` | `string` | yes | The full key. When `revealable` is false this is the only time it is shown, so store it now. | | `scopes` | `("diagrams" \| "audit:read")[]` | yes | `diagrams`: read the library, publish, and change its diagrams, which every key made before scopes holds. `audit:read`: read and export the audit log, and nothing else unless `diagrams` is also given. | ## CreateOrgApiKeyBody | Field | Type | Required | Description | |---|---|---|---| | `name` | `string` | yes | at least 1 character; at most 60 characters; pattern `\S`. | | `scopes` | `("diagrams" \| "audit:read")[]` | no | What the key may do. Left out, `["diagrams"]`. A key for a SIEM takes `["audit:read"]` alone. | ## OrgBrand | Field | Type | Required | Description | |---|---|---|---| | `brand` | `BrandColors` | yes | The saved brand, or null when none is saved. | | `brand.primary` | `string` | yes | Lowercase #rrggbb. | | `brand.secondary` | `string` | yes | Lowercase #rrggbb. | | `brand.accent` | `string` | yes | Lowercase #rrggbb. | | `chosen` | `("primary" \| "secondary" \| "accent")[]` | yes | The colors the person chose. The others are made from the primary. | | `active` | `boolean` | yes | True when a brand is saved and the owner's plan includes brand themes, so the hosted service paints with it. | | `canEdit` | `boolean` | yes | True when the caller's role may save, remove and preview a brand: the owner and admins. | | `plan` | `"free" \| "pro" \| "enterprise"` | yes | The organisation's plan, which is its owner's. | ## BrandColors The saved brand, or null when none is saved. | Field | Type | Required | Description | |---|---|---|---| | `primary` | `string` | yes | Lowercase #rrggbb. | | `secondary` | `string` | yes | Lowercase #rrggbb. | | `accent` | `string` | yes | Lowercase #rrggbb. | ## SavedBrand | Field | Type | Required | Description | |---|---|---|---| | `brand` | `any` | yes | | | `brand.primary` | `string` | yes | Lowercase #rrggbb. | | `brand.secondary` | `string` | yes | Lowercase #rrggbb. | | `brand.accent` | `string` | yes | Lowercase #rrggbb. | | `chosen` | `("primary" \| "secondary" \| "accent")[]` | yes | | | `check` | `BrandCheck` | yes | | | `check.issues` | `BrandIssue[]` | yes | | | `check.issues[].role` | `"primary" \| "secondary" \| "accent"` | yes | | | `check.issues[].severity` | `"error" \| "warning" \| "note"` | yes | An error stops a save. A warning and a note are advice. | | `check.issues[].code` | `"not-a-color" \| "missing-primary" \| "reads-as-status" \| "too-similar" \| "adjusted"` | yes | | | `check.issues[].message` | `string` | yes | | | `check.swatches` | `object \| null` | yes | The color each scheme draws for each role, made darker or lighter where it would not keep 3:1 against the surface. | | `check.swatches.light` | `object` | yes | | | `check.swatches.dark` | `object` | yes | | ## BrandCheck | Field | Type | Required | Description | |---|---|---|---| | `issues` | `BrandIssue[]` | yes | | | `issues[].role` | `"primary" \| "secondary" \| "accent"` | yes | | | `issues[].severity` | `"error" \| "warning" \| "note"` | yes | An error stops a save. A warning and a note are advice. | | `issues[].code` | `"not-a-color" \| "missing-primary" \| "reads-as-status" \| "too-similar" \| "adjusted"` | yes | | | `issues[].message` | `string` | yes | | | `swatches` | `object \| null` | yes | The color each scheme draws for each role, made darker or lighter where it would not keep 3:1 against the surface. | | `swatches.light` | `object` | yes | | | `swatches.light.primary` | `object` | yes | | | `swatches.light.secondary` | `object` | yes | | | `swatches.light.accent` | `object` | yes | | | `swatches.dark` | `object` | yes | | | `swatches.dark.primary` | `object` | yes | | | `swatches.dark.secondary` | `object` | yes | | | `swatches.dark.accent` | `object` | yes | | ## BrandIssue | Field | Type | Required | Description | |---|---|---|---| | `role` | `"primary" \| "secondary" \| "accent"` | yes | | | `severity` | `"error" \| "warning" \| "note"` | yes | An error stops a save. A warning and a note are advice. | | `code` | `"not-a-color" \| "missing-primary" \| "reads-as-status" \| "too-similar" \| "adjusted"` | yes | | | `message` | `string` | yes | | ## BrandBody | Field | Type | Required | Description | |---|---|---|---| | `primary` | `string \| null` | no | The main brand color, as #rrggbb or #rgb in any case. Takes the place of the blue tone. A brand needs one. at most 32 characters. | | `secondary` | `string \| null` | no | Takes the place of the violet tone. Left out or null, it is made from the primary by turning its hue, and follows the primary. at most 32 characters. | | `accent` | `string \| null` | no | Takes the place of the aqua tone. Left out or null, it is made from the primary by turning its hue, and follows the primary. at most 32 characters. | ## BrandPreview | Field | Type | Required | Description | |---|---|---|---| | `check` | `BrandCheck` | yes | | | `check.issues` | `BrandIssue[]` | yes | | | `check.issues[].role` | `"primary" \| "secondary" \| "accent"` | yes | | | `check.issues[].severity` | `"error" \| "warning" \| "note"` | yes | An error stops a save. A warning and a note are advice. | | `check.issues[].code` | `"not-a-color" \| "missing-primary" \| "reads-as-status" \| "too-similar" \| "adjusted"` | yes | | | `check.issues[].message` | `string` | yes | | | `check.swatches` | `object \| null` | yes | The color each scheme draws for each role, made darker or lighter where it would not keep 3:1 against the surface. | | `check.swatches.light` | `object` | yes | | | `check.swatches.dark` | `object` | yes | | | `pictures` | `BrandPreviewPicture[]` | yes | | | `pictures[].id` | `string` | yes | | | `pictures[].title` | `string` | yes | | | `pictures[].light` | `string` | yes | The sample as an SVG in the light scheme. | | `pictures[].dark` | `string` | yes | The sample as an SVG in the dark scheme. | ## BrandPreviewPicture | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `title` | `string` | yes | | | `light` | `string` | yes | The sample as an SVG in the light scheme. | | `dark` | `string` | yes | The sample as an SVG in the dark scheme. | ## AccountBrand | Field | Type | Required | Description | |---|---|---|---| | `chosen` | `object \| null` | yes | The organisation whose brand and custom icons the account's own calls use, or null. | | `chosen.id` | `string` | yes | | | `chosen.slug` | `string` | yes | | | `chosen.name` | `string` | yes | | | `available` | `object[]` | yes | The organisations the account belongs to that have a saved brand or custom icons, by name. | | `available[].id` | `string` | yes | | | `available[].slug` | `string` | yes | | | `available[].name` | `string` | yes | | | `available[].active` | `boolean` | yes | True when the organisation has a saved brand and the owner's plan includes brand themes. | | `available[].iconsActive` | `boolean` | yes | True when the organisation has custom icons and the owner's plan includes them. | ## ChooseBrandBody | Field | Type | Required | Description | |---|---|---|---| | `organization` | `string \| null` | yes | An organisation's id or slug, or null for no brand. The account must be a member and the organisation must have a saved brand. at least 1 character; at most 64 characters. | ## OrgStyleRules | Field | Type | Required | Description | |---|---|---|---| | `rules` | `StyleRules \| null` | yes | The saved rules as the engine read them (the format of docs/architecture/repository-settings.md), or null when none are saved. | | `text` | `string \| null` | yes | The saved rules as the person wrote them, or null. | | `active` | `boolean` | yes | True when rules are saved and the owner's plan includes saved style rules, so CI that reads them applies them. | | `canEdit` | `boolean` | yes | True when the caller's role may save and remove the rules. | | `plan` | `"free" \| "pro" \| "enterprise"` | yes | The organisation's plan, which is its owner's. | | `updatedAt` | `integer \| null` | yes | When the rules were last saved, in milliseconds since the epoch, or null. | ## StyleRules A team's style rules for its Plotwork diagrams, with the version of the format. | Field | Type | Required | Description | |---|---|---|---| | `$schema` | `string` | no | at most 500 characters. | | `version` | `1` | yes | | | `level` | `"error" \| "warning"` | no | error fails a check; warning is reported and passes. Defaults to the file's level, which defaults to error. | | `requireTitle` | `boolean` | no | Every diagram has a title in its header. | | `requireDescription` | `boolean` | no | Every diagram has desc= in its header, the text screen readers announce. | | `maxNodes` | `integer` | no | The most nodes one diagram may hold. minimum 1; maximum 10000. | | `forbidKinds` | `("flow" \| "sequence" \| "schema" \| "git" \| "gantt" \| "class" \| "state" \| "timeline" \| "journey" \| "mindmap" \| "treemap" \| "pie" \| "quadrant" \| "layers" \| "c4" \| "matrix" \| "sankey" \| "xychart" \| "tree" \| "swimlane")[]` | no | Kinds of diagram the team does not use. | | `ids` | `object` | no | A pattern every node id must match. | | `ids.pattern` | `string` | yes | A JavaScript regular expression, without slashes, such as ^[a-z][a-z0-9_]*$. No nested repeats and no back references. at least 1 character; at most 200 characters. | | `ids.kinds` | `("flow" \| "sequence" \| "schema" \| "git" \| "gantt" \| "class" \| "state" \| "timeline" \| "journey" \| "mindmap" \| "treemap" \| "pie" \| "quadrant" \| "layers" \| "c4" \| "matrix" \| "sankey" \| "xychart" \| "tree" \| "swimlane")[]` | no | Only diagrams of these kinds; every kind when left out. | | `ids.hint` | `string` | no | What to tell the author who breaks the rule. at least 1 character; at most 300 characters. | | `ids.level` | `"error" \| "warning"` | no | error fails a check; warning is reported and passes. Defaults to the file's level, which defaults to error. | | `labels` | `object` | no | A pattern every node label must match. | | `labels.pattern` | `string` | yes | A JavaScript regular expression, without slashes, such as ^[a-z][a-z0-9_]*$. No nested repeats and no back references. at least 1 character; at most 200 characters. | | `labels.kinds` | `("flow" \| "sequence" \| "schema" \| "git" \| "gantt" \| "class" \| "state" \| "timeline" \| "journey" \| "mindmap" \| "treemap" \| "pie" \| "quadrant" \| "layers" \| "c4" \| "matrix" \| "sankey" \| "xychart" \| "tree" \| "swimlane")[]` | no | Only diagrams of these kinds; every kind when left out. | | `labels.hint` | `string` | no | What to tell the author who breaks the rule. at least 1 character; at most 300 characters. | | `labels.level` | `"error" \| "warning"` | no | error fails a check; warning is reported and passes. Defaults to the file's level, which defaults to error. | | `icons` | `object[]` | no | Icons required by label. | | `icons[].label` | `string` | yes | Matched against each node label with case ignored, such as postgres\|mysql. at least 1 character; at most 200 characters. | | `icons[].use` | `string[]` | yes | | | `icons[].kinds` | `("flow" \| "sequence" \| "schema" \| "git" \| "gantt" \| "class" \| "state" \| "timeline" \| "journey" \| "mindmap" \| "treemap" \| "pie" \| "quadrant" \| "layers" \| "c4" \| "matrix" \| "sankey" \| "xychart" \| "tree" \| "swimlane")[]` | no | Only diagrams of these kinds; every kind when left out. | | `icons[].hint` | `string` | no | What to tell the author who breaks the rule. at least 1 character; at most 300 characters. | | `icons[].level` | `"error" \| "warning"` | no | error fails a check; warning is reported and passes. Defaults to the file's level, which defaults to error. | | `tones` | `object[]` | no | Tones allowed or forbidden, by node type. | | `tones[].types` | `string[]` | no | Only nodes of these types, as the kind names them (database, service, person, table, container); every node when left out. | | `tones[].allow` | `("neutral" \| "blue" \| "violet" \| "aqua" \| "orange" \| "green" \| "amber" \| "red")[]` | no | | | `tones[].forbid` | `("neutral" \| "blue" \| "violet" \| "aqua" \| "orange" \| "green" \| "amber" \| "red")[]` | no | | | `tones[].kinds` | `("flow" \| "sequence" \| "schema" \| "git" \| "gantt" \| "class" \| "state" \| "timeline" \| "journey" \| "mindmap" \| "treemap" \| "pie" \| "quadrant" \| "layers" \| "c4" \| "matrix" \| "sankey" \| "xychart" \| "tree" \| "swimlane")[]` | no | Only diagrams of these kinds; every kind when left out. | | `tones[].hint` | `string` | no | What to tell the author who breaks the rule. at least 1 character; at most 300 characters. | | `tones[].level` | `"error" \| "warning"` | no | error fails a check; warning is reported and passes. Defaults to the file's level, which defaults to error. | ## StyleRulesBody | Field | Type | Required | Description | |---|---|---|---| | `text` | `string` | yes | The rules file as JSON text, at most 32768 characters. The style rules guide lists its fields. at least 1 character; at most 32768 characters. | ## OrgIcons | Field | Type | Required | Description | |---|---|---|---| | `icons` | `OrgIcon[]` | yes | | | `icons[].name` | `string` | yes | | | `icons[].ref` | `string` | yes | What a diagram writes to use it. | | `icons[].aliases` | `string[]` | yes | | | `icons[].mediaType` | `"image/svg+xml" \| "image/png"` | yes | | | `icons[].image` | `string` | yes | The checked picture as a data: address, for a preview. | | `icons[].width` | `number` | yes | | | `icons[].height` | `number` | yes | | | `icons[].bytes` | `integer` | yes | | | `icons[].updatedAt` | `integer` | yes | Milliseconds since the epoch. | | `active` | `boolean` | yes | True when the owner's plan includes custom icons, so the hosted service draws them. | | `canEdit` | `boolean` | yes | True when the caller's role may upload, replace and delete icons. | | `plan` | `"free" \| "pro" \| "enterprise"` | yes | | | `limit` | `integer` | yes | How many icons the plan lets the organisation keep. | ## OrgIcon | Field | Type | Required | Description | |---|---|---|---| | `name` | `string` | yes | | | `ref` | `string` | yes | What a diagram writes to use it. | | `aliases` | `string[]` | yes | | | `mediaType` | `"image/svg+xml" \| "image/png"` | yes | | | `image` | `string` | yes | The checked picture as a data: address, for a preview. | | `width` | `number` | yes | | | `height` | `number` | yes | | | `bytes` | `integer` | yes | | | `updatedAt` | `integer` | yes | Milliseconds since the epoch. | ## SavedIcon | Field | Type | Required | Description | |---|---|---|---| | `icon` | `OrgIcon` | yes | | | `icon.name` | `string` | yes | | | `icon.ref` | `string` | yes | What a diagram writes to use it. | | `icon.aliases` | `string[]` | yes | | | `icon.mediaType` | `"image/svg+xml" \| "image/png"` | yes | | | `icon.image` | `string` | yes | The checked picture as a data: address, for a preview. | | `icon.width` | `number` | yes | | | `icon.height` | `number` | yes | | | `icon.bytes` | `integer` | yes | | | `icon.updatedAt` | `integer` | yes | Milliseconds since the epoch. | | `removed` | `string[]` | yes | What the check left out of the file because it draws nothing, such as editor metadata. | ## IconBody One file (svg or png), or neither to change only the aliases of an icon that exists. | Field | Type | Required | Description | |---|---|---|---| | `svg` | `string` | no | The SVG file's text, at most 32 KB. It is rewritten from an allowlist before it is stored. at most 49152 characters. | | `png` | `string` | no | The PNG file's bytes in base64, at most 32 KB and 1024 pixels on a side. at most 49152 characters. | | `aliases` | `string[]` | no | Other names search and automatic matching find the icon by, at most 10. Left out, the icon has none. | ## IconPreview | Field | Type | Required | Description | |---|---|---|---| | `ref` | `string` | yes | What a diagram writes to use the icon. | | `source` | `string` | yes | The diagram source the preview draws, which uses the icon on one box. | | `light` | `string` | yes | The preview drawn on light, as SVG text. | | `dark` | `string` | yes | The preview drawn on dark, as SVG text. | ## SavedAvatar | Field | Type | Required | Description | |---|---|---|---| | `avatar` | `OrgAvatar` | yes | The organisation's uploaded avatar, or null when it has none and a mark made from its id stands in. | | `avatar.version` | `string` | yes | Changes with every upload. Put it in the address (?v=) so a new picture is a new address. | | `avatar.contentType` | `"image/svg+xml" \| "image/png" \| "image/gif" \| "image/webp"` | yes | | | `avatar.animated` | `boolean` | yes | True when it moves, so a page can hold it still for a person who asked for less motion. | | `removed` | `string[]` | yes | What the check left out of the file because it draws nothing, such as editor metadata, EXIF or XMP. | ## AvatarBody One file: svg, or image. | Field | Type | Required | Description | |---|---|---|---| | `svg` | `string` | no | The SVG file's text, at most 256 KB, still or animated with SMIL. It is rewritten from an allowlist before it is stored. at most 368640 characters. | | `image` | `string` | no | A PNG (APNG included), GIF or WebP file's bytes in base64, still or animated: at most 256 KB, 512 pixels on a side, and 200 frames. The format is read from the bytes. at most 368640 characters. | ## DriftRunReceipt | Field | Type | Required | Description | |---|---|---|---| | `organization` | `object` | yes | The organisation the run was stored for. With `_` in the path, this says which one the key belongs to. | | `organization.id` | `string` | yes | | | `organization.slug` | `string` | yes | | | `repository` | `string` | yes | The repository, as sent. | | `sources` | `integer` | yes | Sources stored. minimum 0. | | `drifted` | `integer` | yes | Sources that drifted. minimum 0. | | `dashboardUrl` | `string` | yes | The organisation's drift dashboard in the portal, for a signed-in member. | ## DriftRunBody | Field | Type | Required | Description | |---|---|---|---| | `repository` | `string` | yes | The repository the run checked, as owner/name. at most 140 characters; pattern `^[A-Za-z0-9][A-Za-z0-9-]{0,38}\/[A-Za-z0-9._-]{1,100}$`. | | `ref` | `string \| null` | yes | The git ref the run checked, or null. at least 1 character; at most 255 characters; pattern `^[^\p{Cc}]+$/u`. | | `sha` | `string \| null` | yes | The commit the run checked, or null. pattern `^[0-9a-fA-F]{7,64}$`. | | `event` | `string \| null` | yes | The event that started the run, or null. at least 1 character; at most 64 characters; pattern `^[a-z][a-z0-9_]*$`. | | `runUrl` | `string \| null` | yes | The run on GitHub. An https address of at most 500 characters, or null. at most 500 characters. | | `pullRequestUrl` | `string \| null` | yes | The pull request the run opened or checked. An https address of at most 500 characters, or null. at most 500 characters. | | `sources` | `DriftSourceBody[]` | yes | Every source the run checked, at most 250. A source of the repository that a run leaves out is removed from the dashboard. | | `sources[].name` | `string` | yes | The source's name as the repository's settings write it, 1 to 120 characters. Unique within a run. at least 1 character; at most 120 characters; pattern `^[^\p{Cc}]+$/u`. | | `sources[].out` | `string` | yes | The diagram's path in the repository, 1 to 300 characters. at least 1 character; at most 300 characters; pattern `^[^\p{Cc}]+$/u`. | | `sources[].status` | `"in-step" \| "drifted" \| "written" \| "failed"` | yes | What the check found: the diagram matches its files (in-step), differs (drifted), was written by the run (written), or could not be made (failed). | | `sources[].added` | `integer` | yes | Items in the files that the diagram does not have. minimum 0; maximum 1000000. | | `sources[].removed` | `integer` | yes | Items in the diagram that the files no longer have. minimum 0; maximum 1000000. | | `sources[].changed` | `integer` | yes | Items both have that differ. minimum 0; maximum 1000000. | | `sources[].errors` | `integer` | yes | Errors the run met making this diagram. minimum 0; maximum 1000000. | | `sources[].owners` | `string[]` | yes | The owners CODEOWNERS gives the diagram's path, as it writes them: at most 20, each @user, @org/team or an email address. | ## DriftSourceBody | Field | Type | Required | Description | |---|---|---|---| | `name` | `string` | yes | The source's name as the repository's settings write it, 1 to 120 characters. Unique within a run. at least 1 character; at most 120 characters; pattern `^[^\p{Cc}]+$/u`. | | `out` | `string` | yes | The diagram's path in the repository, 1 to 300 characters. at least 1 character; at most 300 characters; pattern `^[^\p{Cc}]+$/u`. | | `status` | `"in-step" \| "drifted" \| "written" \| "failed"` | yes | What the check found: the diagram matches its files (in-step), differs (drifted), was written by the run (written), or could not be made (failed). | | `added` | `integer` | yes | Items in the files that the diagram does not have. minimum 0; maximum 1000000. | | `removed` | `integer` | yes | Items in the diagram that the files no longer have. minimum 0; maximum 1000000. | | `changed` | `integer` | yes | Items both have that differ. minimum 0; maximum 1000000. | | `errors` | `integer` | yes | Errors the run met making this diagram. minimum 0; maximum 1000000. | | `owners` | `string[]` | yes | The owners CODEOWNERS gives the diagram's path, as it writes them: at most 20, each @user, @org/team or an email address. | ## OrgDrift | Field | Type | Required | Description | |---|---|---|---| | `active` | `boolean` | yes | True when the owner's plan includes the drift dashboard. When false the lists are empty and run reports are refused. | | `plan` | `"free" \| "pro" \| "enterprise"` | yes | The organisation's plan, which is its owner's. | | `retentionDays` | `integer` | yes | Days a run and a status are kept after they were reported, from the owner's plan. Zero on a plan without the dashboard. minimum 0. | | `repositories` | `DriftRepository[]` | yes | Every repository with a stored status, by name. | | `repositories[].repository` | `string` | yes | | | `repositories[].sources` | `integer` | yes | minimum 0. | | `repositories[].inStep` | `integer` | yes | minimum 0. | | `repositories[].drifted` | `integer` | yes | minimum 0. | | `repositories[].written` | `integer` | yes | minimum 0. | | `repositories[].failed` | `integer` | yes | minimum 0. | | `repositories[].ref` | `string \| null` | yes | | | `repositories[].sha` | `string \| null` | yes | | | `repositories[].runUrl` | `string \| null` | yes | The run on GitHub, or null. | | `repositories[].pullRequestUrl` | `string \| null` | yes | The pull request, or null. | | `repositories[].checkedAt` | `integer` | yes | When the run was reported, in milliseconds since the epoch. | | `sources` | `DriftSource[]` | yes | The latest status of every source: failed first, then drifted, written and in step, each by repository and name. | | `sources[].repository` | `string` | yes | | | `sources[].name` | `string` | yes | | | `sources[].out` | `string` | yes | | | `sources[].status` | `"in-step" \| "drifted" \| "written" \| "failed"` | yes | | | `sources[].added` | `integer` | yes | minimum 0. | | `sources[].removed` | `integer` | yes | minimum 0. | | `sources[].changed` | `integer` | yes | minimum 0. | | `sources[].errors` | `integer` | yes | minimum 0. | | `sources[].owners` | `string[]` | yes | The owners CODEOWNERS gave the path, as written there. | | `sources[].ref` | `string \| null` | yes | | | `sources[].sha` | `string \| null` | yes | | | `sources[].runUrl` | `string \| null` | yes | The run on GitHub, or null. | | `sources[].pullRequestUrl` | `string \| null` | yes | The pull request, or null. | | `sources[].checkedAt` | `integer` | yes | When the run was reported, in milliseconds since the epoch. | | `truncated` | `boolean` | yes | True when there were more sources than one answer carries. | | `runs` | `DriftRun[]` | yes | The latest runs, newest first. | | `runs[].id` | `string` | yes | | | `runs[].repository` | `string` | yes | | | `runs[].event` | `string \| null` | yes | | | `runs[].sources` | `integer` | yes | minimum 0. | | `runs[].drifted` | `integer` | yes | minimum 0. | | `runs[].failed` | `integer` | yes | minimum 0. | | `runs[].errors` | `integer` | yes | minimum 0. | | `runs[].ref` | `string \| null` | yes | | | `runs[].sha` | `string \| null` | yes | | | `runs[].runUrl` | `string \| null` | yes | The run on GitHub, or null. | | `runs[].pullRequestUrl` | `string \| null` | yes | The pull request, or null. | | `runs[].checkedAt` | `integer` | yes | When the run was reported, in milliseconds since the epoch. | ## DriftRepository | Field | Type | Required | Description | |---|---|---|---| | `repository` | `string` | yes | | | `sources` | `integer` | yes | minimum 0. | | `inStep` | `integer` | yes | minimum 0. | | `drifted` | `integer` | yes | minimum 0. | | `written` | `integer` | yes | minimum 0. | | `failed` | `integer` | yes | minimum 0. | | `ref` | `string \| null` | yes | | | `sha` | `string \| null` | yes | | | `runUrl` | `string \| null` | yes | The run on GitHub, or null. | | `pullRequestUrl` | `string \| null` | yes | The pull request, or null. | | `checkedAt` | `integer` | yes | When the run was reported, in milliseconds since the epoch. | ## DriftSource | Field | Type | Required | Description | |---|---|---|---| | `repository` | `string` | yes | | | `name` | `string` | yes | | | `out` | `string` | yes | | | `status` | `"in-step" \| "drifted" \| "written" \| "failed"` | yes | | | `added` | `integer` | yes | minimum 0. | | `removed` | `integer` | yes | minimum 0. | | `changed` | `integer` | yes | minimum 0. | | `errors` | `integer` | yes | minimum 0. | | `owners` | `string[]` | yes | The owners CODEOWNERS gave the path, as written there. | | `ref` | `string \| null` | yes | | | `sha` | `string \| null` | yes | | | `runUrl` | `string \| null` | yes | The run on GitHub, or null. | | `pullRequestUrl` | `string \| null` | yes | The pull request, or null. | | `checkedAt` | `integer` | yes | When the run was reported, in milliseconds since the epoch. | ## DriftRun | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `repository` | `string` | yes | | | `event` | `string \| null` | yes | | | `sources` | `integer` | yes | minimum 0. | | `drifted` | `integer` | yes | minimum 0. | | `failed` | `integer` | yes | minimum 0. | | `errors` | `integer` | yes | minimum 0. | | `ref` | `string \| null` | yes | | | `sha` | `string \| null` | yes | | | `runUrl` | `string \| null` | yes | The run on GitHub, or null. | | `pullRequestUrl` | `string \| null` | yes | The pull request, or null. | | `checkedAt` | `integer` | yes | When the run was reported, in milliseconds since the epoch. | ## OrgAuditPage | Field | Type | Required | Description | |---|---|---|---| | `items` | `OrgAuditEvent[]` | yes | | | `items[].version` | `1` | yes | The version of this shape. A change that is not only an addition gets a new number. | | `items[].id` | `string` | yes | | | `items[].orgId` | `string` | yes | | | `items[].at` | `string` | yes | When it happened: an ISO 8601 time in UTC with milliseconds. | | `items[].action` | `"orgs.org.create" \| "orgs.org.rename" \| "orgs.member.add" \| "orgs.member.remove" \| "orgs.member.leave" \| "orgs.member.role" \| "orgs.owner.transfer" \| "orgs.invite.create" \| "orgs.invite.revoke" \| "orgs.invite.accept" \| "orgs.key.create" \| "orgs.key.reveal" \| "orgs.key.revoke" \| "orgs.diagram.publish" \| "orgs.diagram.update" \| "orgs.diagram.edit" \| "orgs.diagram.restore" \| "orgs.diagram.lock" \| "orgs.diagram.unlock" \| "orgs.diagram.delete" \| "orgs.folder_rule.set" \| "orgs.folder_rule.clear" \| "orgs.brand.set" \| "orgs.brand.clear" \| "orgs.icon.set" \| "orgs.icon.delete" \| "orgs.avatar.set" \| "orgs.avatar.clear" \| "orgs.style_rules.set" \| "orgs.style_rules.clear" \| "orgs.settings.editable" \| "orgs.settings.publishing" \| "orgs.publishing.move" \| "orgs.publishing.unpublish" \| "orgs.drift.report" \| "orgs.drift.settings" \| "orgs.integration.create" \| "orgs.integration.update" \| "orgs.integration.delete" \| "orgs.contract.set" \| "orgs.contract.end" \| "orgs.audit.export" \| "orgs.sso.signin" \| "orgs.sso.config" \| "orgs.sso.domain_add" \| "orgs.sso.domain_verify" \| "orgs.sso.domain_remove" \| "orgs.sso.signin_failed" \| "orgs.sso.jit" \| "orgs.sso.break_glass" \| "orgs.scim.token" \| "orgs.scim.user_create" \| "orgs.scim.user_update" \| "orgs.scim.user_deactivate" \| "orgs.scim.user_delete" \| "orgs.scim.group" \| "orgs.scim.role"` | yes | What happened. Never reused for another meaning. | | `items[].actor` | `object` | yes | | | `items[].actor.kind` | `"user" \| "key" \| "ci" \| "operator"` | yes | | | `items[].actor.id` | `string \| null` | yes | The account id (`user`), the key id (`key`, `ci`), or null (`operator`). | | `items[].actor.name` | `string \| null` | yes | The address or key name when it was recorded, "Plotwork support" for the operator, null once a deleted account is forgotten. | | `items[].target` | `object` | yes | | | `items[].target.type` | `"org" \| "member" \| "invite" \| "key" \| "diagram" \| "folder" \| "brand" \| "icon" \| "avatar" \| "style_rules" \| "settings" \| "drift" \| "contract" \| "sso" \| null` | yes | | | `items[].target.id` | `string \| null` | yes | | | `items[].target.name` | `string \| null` | yes | A member's address, an invited address, a key's, a diagram's or an icon's name, or a folder. | | `items[].metadata` | `object` | yes | A few facts about the event, such as `role` or `keepEditable`. Never a diagram source, a key, a token or a color. | | `items[].ip` | `string \| null` | yes | The network the request came from: the IPv4 /24 or the IPv6 /48, never the full address. | | `items[].userAgent` | `string \| null` | yes | The first 200 characters of the user agent. | | `items[].requestId` | `string \| null` | yes | The request id, which Plotwork support can look up in the API's logs. | | `nextCursor` | `string \| null` | yes | Send as `cursor` for the next, older page; null on the last. | | `retentionDays` | `integer` | yes | How many days the organisation's plan keeps an event. | | `canExport` | `boolean` | yes | Whether the organisation's plan may export the log. | ## OrgAuditEvent | Field | Type | Required | Description | |---|---|---|---| | `version` | `1` | yes | The version of this shape. A change that is not only an addition gets a new number. | | `id` | `string` | yes | | | `orgId` | `string` | yes | | | `at` | `string` | yes | When it happened: an ISO 8601 time in UTC with milliseconds. | | `action` | `"orgs.org.create" \| "orgs.org.rename" \| "orgs.member.add" \| "orgs.member.remove" \| "orgs.member.leave" \| "orgs.member.role" \| "orgs.owner.transfer" \| "orgs.invite.create" \| "orgs.invite.revoke" \| "orgs.invite.accept" \| "orgs.key.create" \| "orgs.key.reveal" \| "orgs.key.revoke" \| "orgs.diagram.publish" \| "orgs.diagram.update" \| "orgs.diagram.edit" \| "orgs.diagram.restore" \| "orgs.diagram.lock" \| "orgs.diagram.unlock" \| "orgs.diagram.delete" \| "orgs.folder_rule.set" \| "orgs.folder_rule.clear" \| "orgs.brand.set" \| "orgs.brand.clear" \| "orgs.icon.set" \| "orgs.icon.delete" \| "orgs.avatar.set" \| "orgs.avatar.clear" \| "orgs.style_rules.set" \| "orgs.style_rules.clear" \| "orgs.settings.editable" \| "orgs.settings.publishing" \| "orgs.publishing.move" \| "orgs.publishing.unpublish" \| "orgs.drift.report" \| "orgs.drift.settings" \| "orgs.integration.create" \| "orgs.integration.update" \| "orgs.integration.delete" \| "orgs.contract.set" \| "orgs.contract.end" \| "orgs.audit.export" \| "orgs.sso.signin" \| "orgs.sso.config" \| "orgs.sso.domain_add" \| "orgs.sso.domain_verify" \| "orgs.sso.domain_remove" \| "orgs.sso.signin_failed" \| "orgs.sso.jit" \| "orgs.sso.break_glass" \| "orgs.scim.token" \| "orgs.scim.user_create" \| "orgs.scim.user_update" \| "orgs.scim.user_deactivate" \| "orgs.scim.user_delete" \| "orgs.scim.group" \| "orgs.scim.role"` | yes | What happened. Never reused for another meaning. | | `actor` | `object` | yes | | | `actor.kind` | `"user" \| "key" \| "ci" \| "operator"` | yes | | | `actor.id` | `string \| null` | yes | The account id (`user`), the key id (`key`, `ci`), or null (`operator`). | | `actor.name` | `string \| null` | yes | The address or key name when it was recorded, "Plotwork support" for the operator, null once a deleted account is forgotten. | | `target` | `object` | yes | | | `target.type` | `"org" \| "member" \| "invite" \| "key" \| "diagram" \| "folder" \| "brand" \| "icon" \| "avatar" \| "style_rules" \| "settings" \| "drift" \| "contract" \| "sso" \| null` | yes | | | `target.id` | `string \| null` | yes | | | `target.name` | `string \| null` | yes | A member's address, an invited address, a key's, a diagram's or an icon's name, or a folder. | | `metadata` | `object` | yes | A few facts about the event, such as `role` or `keepEditable`. Never a diagram source, a key, a token or a color. | | `ip` | `string \| null` | yes | The network the request came from: the IPv4 /24 or the IPv6 /48, never the full address. | | `userAgent` | `string \| null` | yes | The first 200 characters of the user agent. | | `requestId` | `string \| null` | yes | The request id, which Plotwork support can look up in the API's logs. | ## SsoSettings | Field | Type | Required | Description | |---|---|---|---| | `available` | `boolean` | yes | Whether the organisation's plan has single sign-on (Enterprise). | | `plan` | `"free" \| "pro" \| "enterprise"` | yes | | | `redirectUri` | `string` | yes | The redirect URI to register at the identity provider. | | `saml` | `SamlServiceProvider` | yes | | | `saml.entityId` | `string` | yes | Our entity id for this organisation: the audience to set at the provider. | | `saml.acsUrl` | `string` | yes | Our assertion consumer service for this organisation: the single sign-on URL to set at the provider. | | `saml.metadataUrl` | `string` | yes | Our service provider metadata, for a provider that imports it. | | `samlAvailable` | `boolean` | yes | Whether this server can check SAML responses. | | `connection` | `SsoConnection` | yes | | | `connection.protocol` | `"oidc" \| "saml"` | yes | OpenID Connect or SAML 2.0. One connection, one protocol. | | `connection.issuer` | `string` | yes | The OpenID Connect issuer, or the SAML provider's entity id. | | `connection.clientId` | `string \| null` | yes | OpenID Connect only. | | `connection.secretSet` | `boolean` | yes | True when an OpenID Connect client secret is stored. It is never returned. Always false for SAML. | | `connection.tokenAuthMethod` | `"client_secret_basic" \| "client_secret_post" \| null` | yes | | | `connection.saml` | `SamlConnection` | yes | SAML only. | | `connection.saml.entityId` | `string` | yes | The provider's entity id, which every `Issuer` must equal. | | `connection.saml.ssoUrl` | `string` | yes | The provider's sign-in service on the redirect binding. | | `connection.saml.certificates` | `object[]` | yes | | | `connection.saml.emailAttribute` | `string \| null` | yes | The attribute that carries the address, or null when the name identifier does. | | `connection.saml.metadataUrl` | `string \| null` | yes | Where the metadata was fetched from, or null when it was pasted. | | `connection.enabled` | `boolean` | yes | | | `connection.requireSso` | `boolean` | yes | | | `connection.jit` | `boolean` | yes | | | `connection.jitRole` | `"member" \| "reader"` | yes | | | `connection.updatedAt` | `integer` | yes | | | `domains` | `SsoDomain[]` | yes | | | `domains[].id` | `string` | yes | | | `domains[].domain` | `string` | yes | | | `domains[].verified` | `boolean` | yes | | | `domains[].verifiedAt` | `integer \| null` | yes | | | `domains[].checkedAt` | `integer \| null` | yes | | | `domains[].createdAt` | `integer` | yes | | | `domains[].record` | `object` | yes | | | `domains[].record.name` | `string` | yes | | | `domains[].record.type` | `"TXT"` | yes | | | `domains[].record.value` | `string` | yes | | ## SamlServiceProvider | Field | Type | Required | Description | |---|---|---|---| | `entityId` | `string` | yes | Our entity id for this organisation: the audience to set at the provider. | | `acsUrl` | `string` | yes | Our assertion consumer service for this organisation: the single sign-on URL to set at the provider. | | `metadataUrl` | `string` | yes | Our service provider metadata, for a provider that imports it. | ## SsoConnection | Field | Type | Required | Description | |---|---|---|---| | `protocol` | `"oidc" \| "saml"` | yes | OpenID Connect or SAML 2.0. One connection, one protocol. | | `issuer` | `string` | yes | The OpenID Connect issuer, or the SAML provider's entity id. | | `clientId` | `string \| null` | yes | OpenID Connect only. | | `secretSet` | `boolean` | yes | True when an OpenID Connect client secret is stored. It is never returned. Always false for SAML. | | `tokenAuthMethod` | `"client_secret_basic" \| "client_secret_post" \| null` | yes | | | `saml` | `SamlConnection` | yes | SAML only. | | `saml.entityId` | `string` | yes | The provider's entity id, which every `Issuer` must equal. | | `saml.ssoUrl` | `string` | yes | The provider's sign-in service on the redirect binding. | | `saml.certificates` | `object[]` | yes | | | `saml.certificates[].fingerprint` | `string` | yes | SHA-256 of the certificate, as providers show it. | | `saml.certificates[].notAfter` | `integer` | yes | When the certificate expires, in milliseconds since the epoch. Shown, not enforced. | | `saml.emailAttribute` | `string \| null` | yes | The attribute that carries the address, or null when the name identifier does. | | `saml.metadataUrl` | `string \| null` | yes | Where the metadata was fetched from, or null when it was pasted. | | `enabled` | `boolean` | yes | | | `requireSso` | `boolean` | yes | | | `jit` | `boolean` | yes | | | `jitRole` | `"member" \| "reader"` | yes | | | `updatedAt` | `integer` | yes | | ## SamlConnection SAML only. | Field | Type | Required | Description | |---|---|---|---| | `entityId` | `string` | yes | The provider's entity id, which every `Issuer` must equal. | | `ssoUrl` | `string` | yes | The provider's sign-in service on the redirect binding. | | `certificates` | `object[]` | yes | | | `certificates[].fingerprint` | `string` | yes | SHA-256 of the certificate, as providers show it. | | `certificates[].notAfter` | `integer` | yes | When the certificate expires, in milliseconds since the epoch. Shown, not enforced. | | `emailAttribute` | `string \| null` | yes | The attribute that carries the address, or null when the name identifier does. | | `metadataUrl` | `string \| null` | yes | Where the metadata was fetched from, or null when it was pasted. | ## SsoDomain | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `domain` | `string` | yes | | | `verified` | `boolean` | yes | | | `verifiedAt` | `integer \| null` | yes | | | `checkedAt` | `integer \| null` | yes | | | `createdAt` | `integer` | yes | | | `record` | `object` | yes | | | `record.name` | `string` | yes | | | `record.type` | `"TXT"` | yes | | | `record.value` | `string` | yes | | ## SsoConnectionBody | Field | Type | Required | Description | |---|---|---|---| | `issuer` | `string` | yes | The issuer URL of your own tenant. at least 9 characters; at most 300 characters. | | `clientId` | `string` | yes | at least 1 character; at most 255 characters. | | `clientSecret` | `string` | no | Required the first time. Left out later, the stored one is kept. Never returned. at least 1 character; at most 1000 characters. | ## SsoSwitchesBody | Field | Type | Required | Description | |---|---|---|---| | `enabled` | `boolean` | no | | | `requireSso` | `boolean` | no | | | `jit` | `boolean` | no | | | `jitRole` | `"member" \| "reader"` | no | | ## SamlConnectionBody | Field | Type | Required | Description | |---|---|---|---| | `metadataUrl` | `string` | no | The address of the provider's metadata, fetched over HTTPS. Give this or `metadataXml`, or neither to keep the stored metadata. at least 9 characters; at most 500 characters. | | `metadataXml` | `string` | no | The provider's metadata XML, pasted. at least 1 character; at most 262144 characters. | | `emailAttribute` | `string \| null` | no | The attribute that carries the address. Null reads the name identifier; left out keeps the stored choice. at least 1 character; at most 200 characters. | ## SsoDomainBody | Field | Type | Required | Description | |---|---|---|---| | `domain` | `string` | yes | at least 3 characters; at most 253 characters. | ## SsoDomainVerify | Field | Type | Required | Description | |---|---|---|---| | `domain` | `SsoDomain` | yes | | | `domain.id` | `string` | yes | | | `domain.domain` | `string` | yes | | | `domain.verified` | `boolean` | yes | | | `domain.verifiedAt` | `integer \| null` | yes | | | `domain.checkedAt` | `integer \| null` | yes | | | `domain.createdAt` | `integer` | yes | | | `domain.record` | `object` | yes | | | `domain.record.name` | `string` | yes | | | `domain.record.type` | `"TXT"` | yes | | | `domain.record.value` | `string` | yes | | | `checked` | `boolean` | yes | False when the resolver did not answer, so nothing was learned. Try again. | ## ScimSettings | Field | Type | Required | Description | |---|---|---|---| | `available` | `boolean` | yes | Whether the organisation's plan includes SCIM (Enterprise). | | `baseUrl` | `string` | yes | The SCIM base URL to give the identity provider. | | `token` | `ScimTokenView` | yes | | | `token.prefix` | `string` | yes | The first characters of the token, to tell it apart. | | `token.createdAt` | `integer` | yes | | | `token.lastUsedAt` | `integer \| null` | yes | When the provider last used it, to the minute. Null until it has. | | `users` | `integer` | yes | How many people the provider manages here, active or not. | | `groups` | `ScimGroupView[]` | yes | | | `groups[].id` | `string` | yes | | | `groups[].displayName` | `string` | yes | | | `groups[].role` | `"admin" \| "member" \| "reader" \| null` | yes | The role the group gives its members, or null when it gives none. | | `groups[].members` | `integer` | yes | | ## ScimTokenView | Field | Type | Required | Description | |---|---|---|---| | `prefix` | `string` | yes | The first characters of the token, to tell it apart. | | `createdAt` | `integer` | yes | | | `lastUsedAt` | `integer \| null` | yes | When the provider last used it, to the minute. Null until it has. | ## ScimGroupView | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `displayName` | `string` | yes | | | `role` | `"admin" \| "member" \| "reader" \| null` | yes | The role the group gives its members, or null when it gives none. | | `members` | `integer` | yes | | ## ScimTokenIssued | Field | Type | Required | Description | |---|---|---|---| | `token` | `string` | yes | The bearer token for the identity provider. Shown once; only its hash is kept. | | `view` | `ScimTokenView` | yes | | | `view.prefix` | `string` | yes | The first characters of the token, to tell it apart. | | `view.createdAt` | `integer` | yes | | | `view.lastUsedAt` | `integer \| null` | yes | When the provider last used it, to the minute. Null until it has. | ## ScimGroupRoleBody | Field | Type | Required | Description | |---|---|---|---| | `role` | `"admin" \| "member" \| "reader" \| null` | yes | | ## SupportSettings | Field | Type | Required | Description | |---|---|---|---| | `available` | `boolean` | yes | True when the organisation's plan includes priority support (Enterprise). | | `plan` | `string` | yes | The organisation's plan. | | `contact` | `object \| null` | yes | The named contact, or null when none is named yet. A request still reaches Plotwork support. | | `contact.name` | `string` | yes | | | `contact.email` | `string` | yes | | | `firstResponseHours` | `integer \| null` | yes | The first response target, in business hours, or null when the contract names none. | | `hours` | `string \| null` | yes | When support is staffed, as the contract says it, or null. | | `uptimeSla` | `boolean` | yes | True when the plan includes an uptime SLA. | | `contract` | `object \| null` | yes | The dates of the contract: for the owner and the admins only, and null for everyone else or a contract set without dates. | | `contract.startsOn` | `string` | yes | The first day of the contract, `YYYY-MM-DD` (UTC). | | `contract.endsOn` | `string` | yes | The last day of the contract, `YYYY-MM-DD` (UTC). | | `contract.autoRenew` | `boolean` | yes | Whether the contract renews by itself at the end. | | `contract.noticeDays` | `integer` | yes | The notice either side gives before the end, in days. | ## SupportAck | Field | Type | Required | Description | |---|---|---|---| | `received` | `true` | yes | | | `contactName` | `string \| null` | yes | The name of the contact who will answer, or null when none is named. | | `firstResponseHours` | `integer \| null` | yes | The first response target, in business hours, or null. | ## SupportRequestBody | Field | Type | Required | Description | |---|---|---|---| | `subject` | `string` | yes | One line on what it is about. 1 to 160 characters. at least 1 character; at most 160 characters. | | `message` | `string` | yes | What happened, what was expected, and anything that helps reproduce it. 1 to 5000 characters; line breaks are kept. at least 1 character; at most 5000 characters. | | `severity` | `"question" \| "broken" \| "urgent"` | yes | `question` for a question, `broken` when something does not work, `urgent` when production is affected. The contact reads the urgent ones first. | ## FindPatternResponse Patterns that match a request, best first, with summaries and no source. | Field | Type | Required | Description | |---|---|---|---| | `query` | `string` | yes | The query that was searched. | | `matches` | `object[]` | yes | Up to the limit, best match first. Empty when nothing fits. | | `matches[].id` | `string` | yes | The pattern id, as find_pattern returns it, such as three-tier-network. at least 1 character; at most 60 characters. | | `matches[].title` | `string` | yes | | | `matches[].summary` | `string` | yes | One sentence: what the diagram shows. | | `matches[].kind` | `string` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `matches[].tags` | `string[]` | yes | | | `matches[].providers` | `("neutral" \| "aws" \| "azure" \| "gcp")[]` | yes | The flavours the pattern comes in. | | `matches[].score` | `number` | yes | How well it matches; only the order of the results is meaningful. | | `next` | `string` | yes | The next step in one sentence. | ## FindPatternInput Words that describe the architecture wanted, and optional filters. | Field | Type | Required | Description | |---|---|---|---| | `query` | `string` | yes | The architecture wanted, in plain words, such as "three tier network". at least 1 character; at most 200 characters. | | `provider` | `"neutral" \| "aws" \| "azure" \| "gcp"` | no | Only patterns that come in this flavour: neutral, aws, azure, or gcp. | | `tags` | `string[]` | no | Tags every result must have, such as "load balancer". Up to 5. | | `kind` | `"flow" \| "sequence" \| "schema" \| "git" \| "gantt" \| "class" \| "state" \| "timeline" \| "journey" \| "mindmap" \| "treemap" \| "pie" \| "quadrant" \| "layers" \| "c4" \| "matrix" \| "sankey" \| "xychart" \| "tree" \| "swimlane"` | no | Only patterns of this diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane. | | `limit` | `integer` | no | Most results to return, up to 5. minimum 1; maximum 5; default 5. | ## GetPatternResponse A pattern filled for one flavour, with its slots and advice. | Field | Type | Required | Description | |---|---|---|---| | `found` | `boolean` | yes | True when the pattern and flavour exist and the slot values are usable. When false, problems says why and nothing else is filled. | | `id` | `string` | yes | The pattern id that was asked for. | | `title` | `string \| null` | yes | | | `patternSummary` | `string \| null` | yes | One sentence: what the pattern shows. | | `whenToUse` | `string \| null` | yes | | | `kind` | `string \| null` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `flavour` | `"neutral" \| "aws" \| "azure" \| "gcp" \| null` | yes | The flavour that was filled. | | `flavours` | `("neutral" \| "aws" \| "azure" \| "gcp")[]` | yes | Every flavour this pattern comes in; empty when the pattern was not found. | | `source` | `string \| null` | yes | Plotwork source, valid and ready to use; null when not found. | | `slots` | `object[]` | yes | Every slot the source uses, with the value it has. | | `slots[].id` | `string` | yes | The slot id; the key to use in slots. | | `slots[].value` | `string` | yes | The text the slot has in the source: the value sent, or the default. | | `slots[].default` | `string` | yes | The default text. | | `slots[].meaning` | `string` | yes | What belongs in this slot. | | `slots[].isDefault` | `boolean` | yes | True when the slot kept its default. | | `notes` | `object \| null` | yes | How to adapt the pattern: what to keep, what to rename, and when to split. | | `notes.keep` | `string` | yes | What to leave exactly as it is. | | `notes.rename` | `string` | yes | What to rename or replace, and how. | | `notes.split` | `string` | yes | When the diagram has grown too big, and where to cut it. | | `problems` | `object[]` | yes | Mistakes in the id, the flavour, or the slot values, each with a fix. Empty when found. | | `problems[].code` | `"unknown-pattern" \| "unknown-flavour" \| "unknown-slot" \| "bad-slot-value" \| "bad-source"` | yes | | | `problems[].message` | `string` | yes | | | `problems[].hint` | `string` | yes | How to fix it. | | `tree` | `object[]` | yes | The groups, overlays, and nodes as drawn, depth first, so you can see what to rename. | | `tree[].depth` | `integer` | yes | How deep the item sits: 0 for the top level. | | `tree[].kind` | `"group" \| "overlay" \| "node" \| "actor" \| "table"` | yes | | | `tree[].id` | `string` | yes | The id written in the source. | | `tree[].label` | `string` | yes | | | `tree[].type` | `string \| null` | yes | The group type, node type, or actor type; null for a table or an untyped group. | | `outline` | `string[]` | yes | The text description of the diagram. | | `next` | `string` | yes | The next step in one sentence. | ## GetPatternInput A pattern id, the flavour to draw, and the slot values to use. | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The pattern id, as find_pattern returns it, such as three-tier-network. at least 1 character; at most 60 characters. | | `provider` | `"neutral" \| "aws" \| "azure" \| "gcp"` | no | neutral (generic glyphs, no vendor names; the default), or aws, azure, or gcp for that provider's icons. Not every pattern comes in every flavour; the answer lists the ones it has. default "neutral". | | `slots` | `object` | no | Slot values keyed by slot id, such as {"office": "Branch office"}: plain text of up to 60 characters, no quotes or line breaks. Omit a slot to keep its default. get_pattern lists the slots and what they mean. | ## DrawFromPatternResponse A pattern filled, checked, and drawn in one call. | Field | Type | Required | Description | |---|---|---|---| | `found` | `boolean` | yes | True when the pattern and flavour exist and the slot values are usable. When false, problems says why and nothing else is filled. | | `id` | `string` | yes | The pattern id that was asked for. | | `title` | `string \| null` | yes | | | `patternSummary` | `string \| null` | yes | One sentence: what the pattern shows. | | `whenToUse` | `string \| null` | yes | | | `kind` | `string \| null` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `flavour` | `"neutral" \| "aws" \| "azure" \| "gcp" \| null` | yes | The flavour that was filled. | | `flavours` | `("neutral" \| "aws" \| "azure" \| "gcp")[]` | yes | Every flavour this pattern comes in; empty when the pattern was not found. | | `source` | `string \| null` | yes | Plotwork source, valid and ready to use; null when not found. | | `slots` | `object[]` | yes | Every slot the source uses, with the value it has. | | `slots[].id` | `string` | yes | The slot id; the key to use in slots. | | `slots[].value` | `string` | yes | The text the slot has in the source: the value sent, or the default. | | `slots[].default` | `string` | yes | The default text. | | `slots[].meaning` | `string` | yes | What belongs in this slot. | | `slots[].isDefault` | `boolean` | yes | True when the slot kept its default. | | `notes` | `object \| null` | yes | How to adapt the pattern: what to keep, what to rename, and when to split. | | `notes.keep` | `string` | yes | What to leave exactly as it is. | | `notes.rename` | `string` | yes | What to rename or replace, and how. | | `notes.split` | `string` | yes | When the diagram has grown too big, and where to cut it. | | `problems` | `object[]` | yes | Mistakes in the id, the flavour, or the slot values, each with a fix. Empty when found. | | `problems[].code` | `"unknown-pattern" \| "unknown-flavour" \| "unknown-slot" \| "bad-slot-value" \| "bad-source"` | yes | | | `problems[].message` | `string` | yes | | | `problems[].hint` | `string` | yes | How to fix it. | | `valid` | `boolean` | yes | True when the source has no errors. When false, nothing was drawn: fix the errors and call again. | | `errors` | `Diagnostic[]` | yes | Every error found in one pass, each with line, column, and a hint. Empty when valid. | | `errors[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `errors[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `errors[].message` | `string` | yes | What is wrong, in one sentence. | | `errors[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `errors[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `errors[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `errors[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `warnings` | `Diagnostic[]` | yes | Quality warnings for a valid diagram: too dense, too wide, text cut, unconnected nodes, and suggestions for an icon or a group type. Empty when invalid. | | `warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `summary` | `string \| null` | yes | One-line description of the diagram, usable as alt text; null when invalid. | | `outline` | `string[]` | yes | The diagram as plain text in reading order, to check it says what you meant; empty when invalid. | | `outlineTruncated` | `boolean` | yes | True when the summary level cut the outline short. Call again with detail "full" to read all of it. | | `size` | `object \| null` | yes | Natural size of the laid-out diagram; null when invalid. | | `size.width` | `integer` | yes | Width in px. minimum 1. | | `size.height` | `integer` | yes | Height in px. minimum 1. | | `quality` | `object` | no | How clean the picture looks (score, grade, measures) and up to three changes that would help; read it instead of looking. Absent when invalid. | | `quality.score` | `integer` | yes | 0 to 100; 100 is a picture with nothing to fix. The same diagram always gets the same score. minimum 0; maximum 100. | | `quality.grade` | `"clean" \| "fair" \| "cluttered"` | yes | clean is 85 or more, fair 60 or more, cluttered below. | | `quality.measures` | `object` | yes | What the score is made from: crossings, lineOverBox, labelOverlaps, smallestTextPx (in an 880px column), aspectRatio, emptySpace (0 to 1), longestLine (px), detour (1 is straight), the counts of boxes, lines, labels, series, and slices, and any measure the kind adds. | | `quality.hints` | `string[]` | yes | Up to three changes, the one worth most first, each naming the statement or option to edit. | | `quality.line` | `string` | yes | The grade, the score, and the first hint in one line. | | `notices` | `Note[]` | no | Information notes, such as an icon alias drawn as the real icon; absent when there is none. | | `notices[].code` | `string` | yes | A stable code, such as I122 for an icon alias. | | `notices[].severity` | `"info"` | yes | A note never stops a drawing and is not a warning. | | `notices[].message` | `string` | yes | | | `notices[].line` | `integer` | yes | minimum 1. | | `notices[].column` | `integer` | yes | minimum 1. | | `notices[].length` | `integer` | yes | minimum 1. | | `notices[].hint` | `string \| null` | yes | The text to write instead. | | `renders` | `object[]` | yes | One picture per requested color scheme, light first; empty when invalid, when svg is never, and when every picture was left out (see picturesOmitted). | | `renders[].theme` | `"light" \| "dark"` | yes | The color scheme this picture is drawn in. | | `renders[].svg` | `string` | yes | Self-contained SVG markup with an accessible title and description. | | `renders[].size` | `Size` | yes | Natural size of a rendered diagram in px. | | `renders[].size.width` | `integer` | yes | Width in px. minimum 1. | | `renders[].size.height` | `integer` | yes | Height in px. minimum 1. | | `picturesOmitted` | `object` | no | Present only when pictures were left out because the answer would have been too big for an AI client to read in one piece. | | `picturesOmitted.themes` | `("light" \| "dark")[]` | yes | The color schemes whose pictures were left out. | | `picturesOmitted.characters` | `integer` | yes | How many characters the pictures left out came to. | | `picturesOmitted.limit` | `integer` | yes | The most characters one answer may hold before the pictures are left out. | | `picturesOmitted.why` | `string` | yes | Why they were left out, and what to do. | | `viewUrl` | `string` | no | A link that shows a valid diagram in a browser; give it to the person who wants to look. The source is in the part after #, never sent to a server, but anyone with the link can read it. Absent when too long; next says so. | | `pngUrl` | `string` | no | The viewUrl with &download=png: opening it in a browser saves the diagram as a PNG for Slack, email, or a wiki. The PNG is drawn in the person's browser, not on a server, so it is a link to open, not an image address to embed. Absent when too long. | | `svgUrl` | `string` | no | The viewUrl with &download=svg: a person opens it to download the SVG. Not a file address for curl; use saveCommand. Absent when too long. | | `saveCommand` | `string` | no | Run in a shell to save the SVG into the repository: the CLI decodes the link locally, fetches nothing, and embeds the source. Set --out to the path; add --theme dark for dark. Absent when too long. | | `next` | `string` | yes | The next step in one sentence. | ## DrawFromPatternInput A pattern id, the flavour and slot values to use, and what to return. | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The pattern id, as find_pattern returns it, such as three-tier-network. at least 1 character; at most 60 characters. | | `provider` | `"neutral" \| "aws" \| "azure" \| "gcp"` | no | neutral (generic glyphs, no vendor names; the default), or aws, azure, or gcp for that provider's icons. Not every pattern comes in every flavour; the answer lists the ones it has. default "neutral". | | `slots` | `object` | no | Slot values keyed by slot id, such as {"office": "Branch office"}: plain text of up to 60 characters, no quotes or line breaks. Omit a slot to keep its default. get_pattern lists the slots and what they mean. | | `themes` | `"light" \| "dark" \| "both"` | no | light, dark, or both (the default): the light picture and the dark one a dark page needs. default "both". | | `detail` | `"summary" \| "full"` | no | summary (the default), or full: the whole outline, and the source embedded in each SVG. default "summary". | | `svg` | `"auto" \| "always" \| "never"` | no | auto (the default) returns the SVGs, but over MCP leaves them out when the answer would be too big for a client (picturesOmitted). always returns them at any size. never draws nothing, which is cheaper. The HTTP API treats auto as always. default "auto". | | `embedSource` | `boolean` | no | Write the source into each SVG so read_diagram can give it back. Set it when you save the SVG in a repository or publish it to edit later; anyone with the file or link can then read the source. default false. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## LocalUsageAck | Field | Type | Required | Description | |---|---|---|---| | `accepted` | `integer` | yes | How many counts were stored. A count sent again replaces the one stored, so a retry never adds twice. minimum 0. | | `ignored` | `integer` | yes | How many counts were dropped because their tool or kind is not one this server knows. A 200 means the client may treat the days as sent. minimum 0. | ## LocalUsageBody | Field | Type | Required | Description | |---|---|---|---| | `schema` | `1` | yes | The version of this request shape. Always 1. | | `client` | `LocalUsageClient` | yes | | | `client.name` | `"plotwork-mcp"` | yes | Always `plotwork-mcp`. | | `client.version` | `string` | yes | The package version, in semver form. at most 40 characters; pattern `^\d{1,6}\.\d{1,6}\.\d{1,6}(?:-[0-9A-Za-z.-]{1,32})?(?:\+[0-9A-Za-z.-]{1,32})?$`. | | `client.os` | `"linux" \| "darwin" \| "win32" \| "other"` | yes | The operating system family. Anything else is `other`. | | `client.node` | `integer` | yes | The Node major version, a whole number. minimum 18; maximum 99. | | `days` | `LocalUsageDay[]` | yes | At most 31 finished days. | | `days[].day` | `string` | yes | A UTC date from 40 days ago to yesterday. Nothing is accepted for today. pattern `^\d{4}-\d{2}-\d{2}$`. | | `days[].id` | `string` | yes | A pseudonym for one install for one month: the first 128 bits of an HMAC-SHA-256 of `plotwork-usage:` and the month, keyed with a secret that never leaves the device. It changes every month, so installs can be counted within a month and cannot be followed from one month to the next. pattern `^[0-9a-f]{32}$`. | | `days[].counts` | `LocalUsageCount[]` | yes | At most 400 counts. A tool, kind, and outcome may appear once a day. | | `days[].counts[].tool` | `string` | yes | The name of the local MCP tool, such as `draw_diagram`. A name this server does not know is ignored, not refused, so an older server accepts a newer package. pattern `^[a-z][a-z0-9_]{0,63}$`. | | `days[].counts[].kind` | `string` | yes | The diagram kind the source declared, such as `flow`, or `none` for a call with no source or one that names no kind. A kind this server does not know is ignored, not refused, so an older server accepts a newer package. pattern `^[a-z][a-z0-9]{0,31}$`. | | `days[].counts[].outcome` | `"ok" \| "rejected" \| "error"` | yes | `ok` produced its answer, `rejected` was refused because of its input, `error` failed inside the tool. | | `days[].counts[].count` | `integer` | yes | A whole number from 1 to 1,000,000. minimum 1; maximum 1000000. | ## LocalUsageClient | Field | Type | Required | Description | |---|---|---|---| | `name` | `"plotwork-mcp"` | yes | Always `plotwork-mcp`. | | `version` | `string` | yes | The package version, in semver form. at most 40 characters; pattern `^\d{1,6}\.\d{1,6}\.\d{1,6}(?:-[0-9A-Za-z.-]{1,32})?(?:\+[0-9A-Za-z.-]{1,32})?$`. | | `os` | `"linux" \| "darwin" \| "win32" \| "other"` | yes | The operating system family. Anything else is `other`. | | `node` | `integer` | yes | The Node major version, a whole number. minimum 18; maximum 99. | ## LocalUsageDay | Field | Type | Required | Description | |---|---|---|---| | `day` | `string` | yes | A UTC date from 40 days ago to yesterday. Nothing is accepted for today. pattern `^\d{4}-\d{2}-\d{2}$`. | | `id` | `string` | yes | A pseudonym for one install for one month: the first 128 bits of an HMAC-SHA-256 of `plotwork-usage:` and the month, keyed with a secret that never leaves the device. It changes every month, so installs can be counted within a month and cannot be followed from one month to the next. pattern `^[0-9a-f]{32}$`. | | `counts` | `LocalUsageCount[]` | yes | At most 400 counts. A tool, kind, and outcome may appear once a day. | | `counts[].tool` | `string` | yes | The name of the local MCP tool, such as `draw_diagram`. A name this server does not know is ignored, not refused, so an older server accepts a newer package. pattern `^[a-z][a-z0-9_]{0,63}$`. | | `counts[].kind` | `string` | yes | The diagram kind the source declared, such as `flow`, or `none` for a call with no source or one that names no kind. A kind this server does not know is ignored, not refused, so an older server accepts a newer package. pattern `^[a-z][a-z0-9]{0,31}$`. | | `counts[].outcome` | `"ok" \| "rejected" \| "error"` | yes | `ok` produced its answer, `rejected` was refused because of its input, `error` failed inside the tool. | | `counts[].count` | `integer` | yes | A whole number from 1 to 1,000,000. minimum 1; maximum 1000000. | ## LocalUsageCount | Field | Type | Required | Description | |---|---|---|---| | `tool` | `string` | yes | The name of the local MCP tool, such as `draw_diagram`. A name this server does not know is ignored, not refused, so an older server accepts a newer package. pattern `^[a-z][a-z0-9_]{0,63}$`. | | `kind` | `string` | yes | The diagram kind the source declared, such as `flow`, or `none` for a call with no source or one that names no kind. A kind this server does not know is ignored, not refused, so an older server accepts a newer package. pattern `^[a-z][a-z0-9]{0,31}$`. | | `outcome` | `"ok" \| "rejected" \| "error"` | yes | `ok` produced its answer, `rejected` was refused because of its input, `error` failed inside the tool. | | `count` | `integer` | yes | A whole number from 1 to 1,000,000. minimum 1; maximum 1000000. | ## SyntaxResponse The diagram language for one scope: the overview, or one kind in full. | Field | Type | Required | Description | |---|---|---|---| | `answer` | `string` | yes | What this answer is: overview, a topic (import-formats), or the kind it describes in full (flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today; later versions add kinds, so expect others). | | `howToGetAKind` | `string` | yes | In plain words: what this answer holds and how to get the rest. | | `kinds` | `string[]` | yes | Header keywords: the first line names the kind. | | `kindDetails` | `object[]` | yes | The overview lists every kind in the order of kinds, with its header, noun, and summary, and a pointer in more. A kind answer holds that kind alone, in full. | | `kindDetails[].kind` | `string` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `kindDetails[].header` | `string` | yes | The header line, such as flow "Title". | | `kindDetails[].noun` | `string` | yes | What the kind's diagrams are called in a sentence, such as flow or database schema. | | `kindDetails[].summary` | `string` | yes | One sentence: what the kind draws. | | `kindDetails[].statements` | `object[]` | no | The statements the kind adds to the header. Empty for a kind whose language the shared tables of this reference already describe. | | `kindDetails[].statements[].syntax` | `string` | yes | The statement with its parts named, such as: task <id> "Name" days=<n>. | | `kindDetails[].statements[].meaning` | `string` | yes | What the statement does, and what it needs. | | `kindDetails[].statements[].example` | `string` | yes | One line that parses. | | `kindDetails[].limits` | `object[]` | no | The quality thresholds the kind adds. Empty for a kind whose limits are in the limits table of this reference. | | `kindDetails[].limits[].name` | `string` | yes | What is counted, such as tasks. | | `kindDetails[].limits[].value` | `integer` | yes | More than this in one diagram draws a quality warning. | | `kindDetails[].limits[].meaning` | `string` | yes | Why the limit exists and what to do past it. | | `kindDetails[].more` | `string` | no | Only in the overview, where statements and limits are left out: what a call with this kind returns that the overview does not, and how to ask for it. | | `nodeTypes` | `object[]` | no | | | `nodeTypes[].type` | `"person" \| "agent" \| "client" \| "service" \| "gateway" \| "function" \| "database" \| "cache" \| "queue" \| "storage" \| "identity" \| "secret" \| "external" \| "step" \| "decision" \| "terminal"` | yes | | | `nodeTypes[].shape` | `"card" \| "pill"` | yes | How the node is drawn. | | `nodeTypes[].isTinted` | `boolean` | yes | True when the card is filled and outlined in its tone, as for decisions. | | `nodeTypes[].defaultTone` | `"neutral" \| "blue" \| "violet" \| "aqua" \| "orange" \| "green" \| "amber" \| "red"` | yes | Color family used when tone= is not set. | | `nodeTypes[].defaultIcon` | `string \| null` | yes | Icon used when icon= is not set, or null for none. | | `nodeTypes[].isDashed` | `boolean` | yes | True when the outline is dashed, as for external systems. | | `nodeTypes[].usableAsActor` | `boolean` | yes | True when a sequence diagram actor may use this type. | | `tones` | `("neutral" \| "blue" \| "violet" \| "aqua" \| "orange" \| "green" \| "amber" \| "red")[]` | no | | | `directions` | `("auto" \| "right" \| "down" \| "left" \| "up")[]` | no | Values for direction= on a flow header. | | `arrows` | `object[]` | no | | | `arrows[].token` | `string` | yes | The arrow as written in source. | | `arrows[].line` | `"solid" \| "dashed" \| "dotted"` | yes | How the line is drawn. | | `arrows[].heads` | `"end" \| "both" \| "none"` | yes | Which ends carry an arrowhead. | | `arrows[].meaning` | `string` | yes | What the arrow says, so the right one is chosen. | | `blockTypes` | `("alt" \| "opt" \| "loop" \| "par")[]` | no | Frames a sequence diagram can wrap around messages. | | `columnFlags` | `("pk" \| "fk" \| "unique" \| "nullable")[]` | no | Flags a schema column row may carry. | | `themes` | `("light" \| "dark")[]` | no | | | `iconSets` | `("lucide" \| "simple" \| "status" \| "aws" \| "gcp" \| "azure" \| "alibaba" \| "oci" \| "ibm" \| "cloudflare" \| "digitalocean" \| "vercel")[]` | no | | | `iconCount` | `integer` | no | Number of bundled icons; use search_icons to find names. | | `iconProviders` | `object[]` | no | Cloud and platform providers, each an icon set holding the provider (set:cloud) and its products. | | `iconProviders[].set` | `"aws" \| "gcp" \| "azure" \| "alibaba" \| "oci" \| "ibm" \| "cloudflare" \| "digitalocean" \| "vercel"` | yes | Icon set prefix, as in aws:lambda. | | `iconProviders[].title` | `string` | yes | The provider the set stands for. | | `iconProviders[].iconCount` | `integer` | yes | Icons in the set: the provider itself (set:cloud) and its products. | | `iconProviders[].example` | `string` | yes | One icon of the set that is bundled, for example aws:lambda. | | `overlay` | `object` | no | A dashed frame around members that may sit in different groups; it owns none of them. | | `overlay.options` | `string[]` | yes | Options an overlay statement takes after its name, as in: overlay id "Name" members=a,b type=scale-group. | | `overlay.frame` | `"solid" \| "dashed" \| "dotted"` | yes | How an overlay is always drawn. | | `overlay.meaning` | `string` | yes | What an overlay is for, so it is used where a group would be wrong. | | `group` | `object` | no | How a flow group looks and what it accepts; its corner tab shows its icon and name. | | `group.options` | `string[]` | yes | Options a group statement takes after its name, as in: group id "Name" type=region provider=aws {. | | `group.defaultTone` | `"neutral" \| "blue" \| "violet" \| "aqua" \| "orange" \| "green" \| "amber" \| "red"` | yes | Color family of a group with no type when tone= is not set. | | `group.example` | `object` | yes | A complete architecture diagram: typed groups nested by provider, a provider icon on every node, stack marks, and a dotted edge. Copy and edit it. | | `group.example.kind` | `string` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `group.example.title` | `string` | yes | | | `group.example.source` | `string` | yes | A complete diagram that is checked by the build; copy and edit it. | | `group.types` | `object[]` | yes | Every group type. A type gives the group a default icon, tone, frame style, and a shade that deepens with nesting; icon= and tone= override them. | | `group.types[].type` | `"cloud" \| "account" \| "region" \| "zone" \| "network" \| "subnet" \| "public-subnet" \| "private-subnet" \| "management-subnet" \| "isolated-subnet" \| "resource-group" \| "cluster" \| "scale-group" \| "service-plan" \| "web-tier" \| "app-tier" \| "data-tier" \| "storage-account" \| "messaging" \| "edge" \| "security" \| "identity-boundary" \| "trust-boundary" \| "observability" \| "ci-cd" \| "environment" \| "cloud-boundary" \| "on-premises" \| "client-side" \| "external-systems"` | yes | Value for type= on a group. | | `group.types[].title` | `string` | yes | What the frame is called in the text description. | | `group.types[].meaning` | `string` | yes | What the author means by this type, so the right one is chosen. | | `group.types[].tone` | `"neutral" \| "blue" \| "violet" \| "aqua" \| "orange" \| "green" \| "amber" \| "red"` | yes | Color family the frame and tab get when tone= is not set. | | `group.types[].frame` | `"solid" \| "dashed" \| "dotted"` | yes | How the outline is drawn. | | `group.types[].icon` | `string` | yes | Icon in the corner tab when the group has no provider. | | `group.types[].providerIcons` | `object` | yes | Icon in the corner tab for each provider whose icon differs from the generic one, keyed by provider set. | | `lineOptions` | `object[]` | no | Options that animate a line (animate=on makes dashes travel toward the arrowhead) or number the steps of a flow or sequence (numbered=on, start=, and step= on a line). All are off by default. | | `lineOptions[].key` | `string` | yes | The option as written, such as animate or step. | | `lineOptions[].scope` | `"header" \| "line"` | yes | header: on the flow or sequence header line. line: on an edge or a message. | | `lineOptions[].values` | `string` | yes | What the option takes. | | `lineOptions[].meaning` | `string` | yes | What it does, and the cases where it is refused. | | `lineOptions[].example` | `string` | yes | One line that parses. | | `labelIcons` | `object[]` | no | Edge labels that get a tick or cross automatically; icon=none on the edge removes it. | | `labelIcons[].label` | `string` | yes | An edge label that gets an icon automatically, matched whole and ignoring case. | | `labelIcons[].icon` | `string` | yes | The icon the label gets. | | `verbIcons` | `object[]` | no | The icons a flow edge label draws when it names a relationship with a verb. icon= on the edge wins, icon=none removes it, and icons=off on the header turns them all off. | | `verbIcons[].icon` | `string` | yes | The icon drawn in the label. | | `verbIcons[].words` | `string[]` | yes | Phrases that draw it: the whole label, or its first words followed by what is acted on ("reads orders"). | | `style` | `string[]` | no | How to make a flow diagram look finished: verbs on arrows, role= on lines that mean something, typed groups, product names. | | `autoIcons` | `object` | no | Items with no icon= get the icon their label names for certain (a brand, a cloud product, a common role). icon=none keeps one item plain; icons=off on the header keeps them all plain. | | `autoIcons.key` | `string` | yes | The header option: icons. | | `autoIcons.values` | `string` | yes | What it takes. | | `autoIcons.meaning` | `string` | yes | What it does, and how one item opts out. | | `autoIcons.example` | `string` | yes | One header line that parses. | | `roleIcons` | `object[]` | no | The plain icons for common roles, drawn on flow and sequence service and external items, C4 elements, layers blocks, mind map topics, and swimlane lanes. The last role word of the label wins. | | `roleIcons[].icon` | `string` | yes | The icon drawn. | | `roleIcons[].words` | `string[]` | yes | Whole words of a label of three words or fewer that draw it. | | `statusIcons` | `object[]` | no | Colored status and outcome icons: a green check, a red X, and so on. | | `statusIcons[].ref` | `string` | yes | Value for icon=, such as status:success. | | `statusIcons[].tone` | `"neutral" \| "blue" \| "violet" \| "aqua" \| "orange" \| "green" \| "amber" \| "red"` | yes | Color the icon is always drawn in, whatever tone its owner has. | | `statusIcons[].meaning` | `string` | yes | What the icon says, so the right one is chosen. | | `limits` | `object` | no | Bounds and quality thresholds. | | `limits.maxSourceLength` | `integer` | yes | | | `limits.maxFlowNodes` | `integer` | yes | Quality lint: more nodes than this warns, for a diagram with no groups. | | `limits.nodesPerGroup` | `integer` | yes | Each group of a flow diagram raises the node limit by this many. | | `limits.maxFlowNodesCeiling` | `integer` | yes | The node limit never rises above this, whatever the groups. | | `limits.maxSchemaTables` | `integer` | yes | | | `limits.maxSequenceActors` | `integer` | yes | | | `limits.maxNumberedSteps` | `integer` | yes | More numbered steps than this in one diagram warns (W200). | | `limits.maxStackMarks` | `integer` | yes | Most technology marks a node carries in stack=. | | `limits.targetWidth` | `integer` | yes | Column width in px that diagrams are checked against. | | `editing` | `object` | no | How to edit an existing Plotwork SVG: read it back with read_diagram, change the source, draw it, and save or publish it again. | | `editing.steps` | `string[]` | yes | In order: how to change a diagram that already exists as a Plotwork SVG, a hosted link or a file in the repository. | | `editing.embedSource` | `string` | yes | When to set embedSource true on draw_diagram, render_diagram, or publish_diagram, and what it means for who can read the source. | | `diagnosticCodes` | `object[]` | no | | | `diagnosticCodes[].name` | `string` | yes | Identifier of the code. | | `diagnosticCodes[].code` | `string` | yes | The code to branch on, such as E120. | | `diagnosticCodes[].severity` | `"error" \| "warning"` | yes | | | `examples` | `object[]` | yes | Tested examples: the overview holds the ones for the kinds the shared tables describe in full, a kind answer holds its own. | | `examples[].kind` | `string` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `examples[].title` | `string` | yes | | | `examples[].source` | `string` | yes | A complete diagram that is checked by the build; copy and edit it. | | `importFormats` | `object[]` | no | Only for topic import-formats: every format import_source reads, a line each. | | `importFormats[].format` | `string` | yes | The format, as import_source takes it. | | `importFormats[].reads` | `string` | yes | What it reads. | | `importFormats[].draws` | `string` | yes | The diagram it draws. | | `importFormats[].options` | `string` | yes | The options it takes. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The same operations as callable tools | | [Language reference](https://docs.plotwork.dev/reference/language.md) | What the diagram source may contain | --- # Draw diagram > Page: https://docs.plotwork.dev/api/post-v1-diagrams-draw. Index of every page: https://docs.plotwork.dev/llms.txt Check, describe, and draw a diagram. `POST /v1/diagrams/draw` ## TL;DR | Question | Answer | |---|---| | What does it do? | Check, describe, and draw a diagram. | | Which tool does the same? | [`draw_diagram`](https://docs.plotwork.dev/mcp/tools/draw-diagram.md) | | What does it take? | A JSON body of type [`DrawDiagramInput`](https://docs.plotwork.dev/api/schemas.md#drawdiagraminput). | | What does it return? | Status 200 with a body of type [`DrawDiagramResponse`](https://docs.plotwork.dev/api/schemas.md#drawdiagramresponse). | | What can go wrong? | `400` the body failed schema validation; `401` the api key is missing or not valid; `403` the account is on the free plan, which does not include the rest drawing and checking routes (plan.rest_not_included); `413` the body is larger than the size cap; `429` too many requests. Every failure uses the one error envelope. | ## What it does Validates the source and, when it is valid, also returns the quality warnings, a plain-text outline, the natural size, and the SVG for the light theme, the dark theme, or both. Source with errors is a normal 200 result with valid false, every error with its line, column, and hint, and nothing drawn, so a caller fixes the source and calls again. Prefer this route over calling the validate, outline, and render routes separately: it does the work of all three in one request. Use the single routes only when one fine-grained step is all you need. ## Request body The body has the type [`DrawDiagramInput`](https://docs.plotwork.dev/api/schemas.md#drawdiagraminput). | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | Diagram source. The first line is the header: flow "Title", sequence "Title", schema "Title", git "Title", gantt "Title", class "Title", state "Title", timeline "Title", journey "Title", mindmap "Title", treemap "Title", pie "Title", quadrant "Title", layers "Title", c4 "Title", matrix "Title", sankey "Title", xychart "Title", tree "Title", or swimlane "Title". at least 1 character; at most 50000 characters. | | `themes` | `"light" \| "dark" \| "both"` | no | light, dark, or both (the default): the light picture and the dark one a dark page needs. default "both". | | `detail` | `"summary" \| "full"` | no | summary (the default), or full: the whole outline, and the source embedded in each SVG. default "summary". | | `svg` | `"auto" \| "always" \| "never"` | no | auto (the default) returns the SVGs, but over MCP leaves them out when the answer would be too big for a client (picturesOmitted). always returns them at any size. never draws nothing, which is cheaper. The HTTP API treats auto as always. default "auto". | | `embedSource` | `boolean` | no | Write the source into each SVG so read_diagram can give it back. Set it when you save the SVG in a repository or publish it to edit later; anyone with the file or link can then read the source. default false. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The check, and when valid the outline and pictures. | [`DrawDiagramResponse`](https://docs.plotwork.dev/api/schemas.md#drawdiagramresponse) | | 400 | The body failed schema validation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | The API key is missing or not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The account is on the Free plan, which does not include the REST drawing and checking routes (plan.rest_not_included). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than the size cap. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`DrawDiagramResponse`](https://docs.plotwork.dev/api/schemas.md#drawdiagramresponse). | Field | Type | Required | Description | |---|---|---|---| | `valid` | `boolean` | yes | True when the source has no errors. When false, nothing was drawn: fix the errors and call again. | | `kind` | `string \| null` | yes | The diagram kind, or null when the header could not be read. | | `errors` | `Diagnostic[]` | yes | Every error found in one pass, each with line, column, and a hint. Empty when valid. | | `errors[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `errors[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `errors[].message` | `string` | yes | What is wrong, in one sentence. | | `errors[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `errors[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `errors[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `errors[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `warnings` | `Diagnostic[]` | yes | Quality warnings for a valid diagram: too dense, too wide, text cut, unconnected nodes, and suggestions for an icon or a group type. Empty when invalid. | | `warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `summary` | `string \| null` | yes | One-line description of the diagram, usable as alt text; null when invalid. | | `outline` | `string[]` | yes | The diagram as plain text in reading order, to check it says what you meant; empty when invalid. | | `outlineTruncated` | `boolean` | yes | True when the summary level cut the outline short. Call again with detail "full" to read all of it. | | `size` | `object \| null` | yes | Natural size of the laid-out diagram; null when invalid. | | `size.width` | `integer` | yes | Width in px. minimum 1. | | `size.height` | `integer` | yes | Height in px. minimum 1. | | `quality` | `object` | no | How clean the picture looks (score, grade, measures) and up to three changes that would help; read it instead of looking. Absent when invalid. | | `quality.score` | `integer` | yes | 0 to 100; 100 is a picture with nothing to fix. The same diagram always gets the same score. minimum 0; maximum 100. | | `quality.grade` | `"clean" \| "fair" \| "cluttered"` | yes | clean is 85 or more, fair 60 or more, cluttered below. | | `quality.measures` | `object` | yes | What the score is made from: crossings, lineOverBox, labelOverlaps, smallestTextPx (in an 880px column), aspectRatio, emptySpace (0 to 1), longestLine (px), detour (1 is straight), the counts of boxes, lines, labels, series, and slices, and any measure the kind adds. | | `quality.hints` | `string[]` | yes | Up to three changes, the one worth most first, each naming the statement or option to edit. | | `quality.line` | `string` | yes | The grade, the score, and the first hint in one line. | | `notices` | `Note[]` | no | Information notes, such as an icon alias drawn as the real icon; absent when there is none. | | `notices[].code` | `string` | yes | A stable code, such as I122 for an icon alias. | | `notices[].severity` | `"info"` | yes | A note never stops a drawing and is not a warning. | | `notices[].message` | `string` | yes | | | `notices[].line` | `integer` | yes | minimum 1. | | `notices[].column` | `integer` | yes | minimum 1. | | `notices[].length` | `integer` | yes | minimum 1. | | `notices[].hint` | `string \| null` | yes | The text to write instead. | | `renders` | `object[]` | yes | One picture per requested color scheme, light first; empty when invalid, when svg is never, and when every picture was left out (see picturesOmitted). | | `renders[].theme` | `"light" \| "dark"` | yes | The color scheme this picture is drawn in. | | `renders[].svg` | `string` | yes | Self-contained SVG markup with an accessible title and description. | | `renders[].size` | `Size` | yes | Natural size of a rendered diagram in px. | | `renders[].size.width` | `integer` | yes | Width in px. minimum 1. | | `renders[].size.height` | `integer` | yes | Height in px. minimum 1. | | `picturesOmitted` | `object` | no | Present only when pictures were left out because the answer would have been too big for an AI client to read in one piece. | | `picturesOmitted.themes` | `("light" \| "dark")[]` | yes | The color schemes whose pictures were left out. | | `picturesOmitted.characters` | `integer` | yes | How many characters the pictures left out came to. | | `picturesOmitted.limit` | `integer` | yes | The most characters one answer may hold before the pictures are left out. | | `picturesOmitted.why` | `string` | yes | Why they were left out, and what to do. | | `viewUrl` | `string` | no | A link that shows a valid diagram in a browser; give it to the person who wants to look. The source is in the part after #, never sent to a server, but anyone with the link can read it. Absent when too long; next says so. | | `pngUrl` | `string` | no | The viewUrl with &download=png: opening it in a browser saves the diagram as a PNG for Slack, email, or a wiki. The PNG is drawn in the person's browser, not on a server, so it is a link to open, not an image address to embed. Absent when too long. | | `svgUrl` | `string` | no | The viewUrl with &download=svg: a person opens it to download the SVG. Not a file address for curl; use saveCommand. Absent when too long. | | `saveCommand` | `string` | no | Run in a shell to save the SVG into the repository: the CLI decodes the link locally, fetches nothing, and embeds the source. Set --out to the path; add --theme dark for dark. Absent when too long. | | `next` | `string` | yes | The next step in one sentence. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [`draw_diagram`](https://docs.plotwork.dev/mcp/tools/draw-diagram.md) | The same operation as an MCP tool | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Edit diagram > Page: https://docs.plotwork.dev/api/post-v1-diagrams-edit. Index of every page: https://docs.plotwork.dev/llms.txt Change diagram source with small typed operations. `POST /v1/diagrams/edit` ## TL;DR | Question | Answer | |---|---| | What does it do? | Change diagram source with small typed operations. | | Which tool does the same? | [`edit_diagram`](https://docs.plotwork.dev/mcp/tools/edit-diagram.md) | | What does it take? | A JSON body of type [`EditDiagramInput`](https://docs.plotwork.dev/api/schemas.md#editdiagraminput). | | What does it return? | Status 200 with a body of type [`EditDiagramResponse`](https://docs.plotwork.dev/api/schemas.md#editdiagramresponse). | | What can go wrong? | `400` the body failed schema validation; `401` the api key is missing or not valid; `403` the account is on the free plan, which does not include the rest drawing and checking routes (plan.rest_not_included); `413` the body is larger than the size cap; `429` too many requests. Every failure uses the one error envelope. | ## What it does Applies operations in order (rename, set, add_node, remove, add_edge, remove_edge, move, replace_text) to the source, on every kind, and returns the new source with a result for each operation and the check of the result. Lines no operation touches come back byte for byte. An operation that cannot apply is reported with a hint and skipped, in a normal 200 answer. With svg never (the default) it parses and outlines the result and lays nothing out; with auto or always it also draws it. ## Request body The body has the type [`EditDiagramInput`](https://docs.plotwork.dev/api/schemas.md#editdiagraminput). | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | Diagram source; the first line is the header, such as flow "Title" (draw_diagram lists every kind). at least 1 character; at most 50000 characters. | | `operations` | `object[]` | yes | Applied in order. | | `operations[].op` | `"rename" \| "set" \| "add_node" \| "remove" \| "add_edge" \| "remove_edge" \| "move" \| "replace_text"` | yes | | | `operations[].target` | `string` | no | Id, label, table.column, or "header". at most 2000 characters. | | `operations[].to` | `string` | no | rename: the new name. Edges: the end. at most 2000 characters. | | `operations[].from` | `string` | no | Edges: the start. at most 2000 characters. | | `operations[].option` | `string` | no | at most 40 characters; pattern `^[a-z][A-Za-z0-9_-]*$`. | | `operations[].value` | `string \| null` | no | set: null removes the option. at most 2000 characters. | | `operations[].id` | `string` | no | at most 2000 characters. | | `operations[].label` | `string` | no | add_edge on sankey: the amount. at most 2000 characters. | | `operations[].type` | `string` | no | add_node: node type, or statement word (c4 person, tree dir). at most 2000 characters. | | `operations[].options` | `object` | no | | | `operations[].statement` | `string` | no | add_node: the whole line, for a kind with no plain node statement. at most 2000 characters. | | `operations[].into` | `string \| null` | no | A block; null is the top level. at most 2000 characters. | | `operations[].after` | `string` | no | at most 2000 characters. | | `operations[].arrow` | `"<-->" \| "-->" \| "<->" \| "..>" \| "->" \| "--"` | no | | | `operations[].find` | `string` | no | at most 2000 characters. | | `operations[].replace` | `string` | no | at most 2000 characters. | | `operations[].all` | `boolean` | no | | | `svg` | `"auto" \| "always" \| "never"` | no | never (the default) draws nothing; auto or always draws like draw_diagram. default "never". | | `themes` | `"light" \| "dark" \| "both"` | no | light, dark, or both (the default): the light picture and the dark one a dark page needs. default "both". | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The edited source, each operation's result, and the check. | [`EditDiagramResponse`](https://docs.plotwork.dev/api/schemas.md#editdiagramresponse) | | 400 | The body failed schema validation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | The API key is missing or not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The account is on the Free plan, which does not include the REST drawing and checking routes (plan.rest_not_included). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than the size cap. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`EditDiagramResponse`](https://docs.plotwork.dev/api/schemas.md#editdiagramresponse). | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | The edited source. Lines no operation touched are byte for byte as they were, comments included. | | `changed` | `boolean` | yes | | | `results` | `object[]` | yes | One entry for each operation, in order. | | `results[].op` | `"rename" \| "set" \| "add_node" \| "remove" \| "add_edge" \| "remove_edge" \| "move" \| "replace_text"` | yes | | | `results[].applied` | `boolean` | yes | | | `results[].lines` | `integer[]` | yes | Lines of the new source this operation wrote, or the lines it removed from. | | `results[].message` | `string` | yes | | | `results[].hint` | `string \| null` | yes | | | `valid` | `boolean` | yes | | | `kind` | `string \| null` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `errors` | `Diagnostic[]` | yes | | | `errors[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `errors[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `errors[].message` | `string` | yes | What is wrong, in one sentence. | | `errors[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `errors[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `errors[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `errors[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `warnings` | `Diagnostic[]` | yes | Quality warnings; only when the result is drawn. | | `warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `summary` | `string \| null` | yes | | | `outline` | `string[]` | yes | | | `outlineTruncated` | `boolean` | yes | | | `size` | `any` | yes | | | `size.width` | `integer` | yes | Width in px. minimum 1. | | `size.height` | `integer` | yes | Height in px. minimum 1. | | `renders` | `object[]` | yes | | | `renders[].theme` | `"light" \| "dark"` | yes | | | `renders[].svg` | `string` | yes | | | `renders[].size` | `Size` | yes | Natural size of a rendered diagram in px. | | `renders[].size.width` | `integer` | yes | Width in px. minimum 1. | | `renders[].size.height` | `integer` | yes | Height in px. minimum 1. | | `picturesOmitted` | `object` | no | Present only when pictures were left out because the answer would have been too big for an AI client to read in one piece. | | `picturesOmitted.themes` | `("light" \| "dark")[]` | yes | The color schemes whose pictures were left out. | | `picturesOmitted.characters` | `integer` | yes | How many characters the pictures left out came to. | | `picturesOmitted.limit` | `integer` | yes | The most characters one answer may hold before the pictures are left out. | | `picturesOmitted.why` | `string` | yes | Why they were left out, and what to do. | | `viewUrl` | `string` | no | A link that shows a valid diagram in a browser; give it to the person who wants to look. The source is in the part after #, never sent to a server, but anyone with the link can read it. Absent when too long; next says so. | | `svgUrl` | `string` | no | The viewUrl with &download=svg: a person opens it to download the SVG. Not a file address for curl; use saveCommand. Absent when too long. | | `saveCommand` | `string` | no | Run in a shell to save the SVG into the repository: the CLI decodes the link locally, fetches nothing, and embeds the source. Set --out to the path; add --theme dark for dark. Absent when too long. | | `next` | `string` | yes | | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [`edit_diagram`](https://docs.plotwork.dev/mcp/tools/edit-diagram.md) | The same operation as an MCP tool | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Convert Mermaid > Page: https://docs.plotwork.dev/api/post-v1-diagrams-convert-mermaid. Index of every page: https://docs.plotwork.dev/llms.txt Convert one Mermaid diagram. `POST /v1/diagrams/convert-mermaid` ## TL;DR | Question | Answer | |---|---| | What does it do? | Convert one Mermaid diagram. | | Which tool does the same? | [`convert_mermaid`](https://docs.plotwork.dev/mcp/tools/convert-mermaid.md) | | What does it take? | A JSON body of type [`ConvertMermaidInput`](https://docs.plotwork.dev/api/schemas.md#convertmermaidinput). | | What does it return? | Status 200 with a body of type [`ConvertMermaidResponse`](https://docs.plotwork.dev/api/schemas.md#convertmermaidresponse). | | What can go wrong? | `400` the body failed schema validation; `401` the api key is missing or not valid; `403` the account is on the free plan, which does not include the rest drawing and checking routes (plan.rest_not_included); `413` the body is larger than the size cap; `429` too many requests. Every failure uses the one error envelope. | ## What it does Converts one Mermaid flowchart, sequence, ER, git, Gantt, class, state, timeline, journey, mind map, treemap, pie, quadrant, C4, sankey, XY chart, or tree view diagram to Plotwork source and validates the result. The answer carries every dropped or changed construct as a diagnostic with a hint, the Plotwork validation with icon and group-type suggestions, the outline, and, when render is true, the SVG for each requested theme. A Mermaid diagram that cannot be read is a normal 200 result with ok false and nothing converted. Prefer this route over converting by hand and then calling the validate, outline, and render routes. To convert every Mermaid fence in a document at once, use the Markdown sync route with convertMermaid set. ## Request body The body has the type [`ConvertMermaidInput`](https://docs.plotwork.dev/api/schemas.md#convertmermaidinput). | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | One Mermaid diagram: a flowchart or graph, sequenceDiagram, erDiagram, gitGraph, gantt, classDiagram, stateDiagram, timeline, journey, mindmap, treemap-beta or treemap, pie, quadrantChart, C4Context or C4Container or C4Component or C4Dynamic or C4Deployment, sankey-beta or sankey, xychart-beta or xychart, or treeView-beta or treeView. Front matter and %%{init}%% directives are allowed and are dropped. Send the text inside a mermaid fence, without the fence. at least 1 character; at most 50000 characters. | | `lenient` | `boolean` | no | false (the default): a line that cannot be read stops the conversion, so nothing is converted with a line missing. true: such a line is skipped with a warning and the rest is converted. default false. | | `render` | `boolean` | no | Also draw the converted diagram, in the color schemes themes names. default false. | | `themes` | `"light" \| "dark" \| "both"` | no | light, dark, or both (the default): the light picture and the dark one a dark page needs. default "both". | | `detail` | `"summary" \| "full"` | no | summary (the default), or full: the whole outline, and the source embedded in each SVG. default "summary". | | `svg` | `"auto" \| "always" \| "never"` | no | auto (the default) returns the SVGs, but over MCP leaves them out when the answer would be too big for a client (picturesOmitted). always returns them at any size. never draws nothing, which is cheaper. The HTTP API treats auto as always. default "auto". | | `embedSource` | `boolean` | no | Write the source into each SVG so read_diagram can give it back. Set it when you save the SVG in a repository or publish it to edit later; anyone with the file or link can then read the source. default false. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The converted source and its diagnostics. | [`ConvertMermaidResponse`](https://docs.plotwork.dev/api/schemas.md#convertmermaidresponse) | | 400 | The body failed schema validation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | The API key is missing or not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The account is on the Free plan, which does not include the REST drawing and checking routes (plan.rest_not_included). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than the size cap. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`ConvertMermaidResponse`](https://docs.plotwork.dev/api/schemas.md#convertmermaidresponse). | Field | Type | Required | Description | |---|---|---|---| | `ok` | `boolean` | yes | True when `source` is valid Plotwork source. False means nothing was converted and `source` is empty: read `diagnostics`. | | `kind` | `string \| null` | yes | The Plotwork kind the diagram became, or null when it could not be converted. | | `mermaidType` | `string \| null` | yes | The Mermaid diagram type read from the first line, such as flowchart or sequenceDiagram; null when none was found. | | `source` | `string` | yes | The Plotwork source, or an empty string when nothing could be converted. | | `diagnostics` | `Diagnostic[]` | yes | Everything dropped, changed, or wrong, with a hint that says what to do. Codes start with M. Lines and columns point into the Mermaid you sent. M1xx codes are errors that stop the conversion; M2xx codes are warnings and the conversion went on. | | `diagnostics[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `diagnostics[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `diagnostics[].message` | `string` | yes | What is wrong, in one sentence. | | `diagnostics[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `diagnostics[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `diagnostics[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `diagnostics[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `stats` | `object` | yes | | | `stats.nodes` | `integer` | yes | Flow nodes written. | | `stats.edges` | `integer` | yes | Flow edges written. | | `stats.groups` | `integer` | yes | Flow groups written. | | `stats.actors` | `integer` | yes | Sequence participants written. | | `stats.messages` | `integer` | yes | Sequence messages written. | | `stats.tables` | `integer` | yes | Schema tables written. | | `stats.columns` | `integer` | yes | Schema columns written. | | `stats.relations` | `integer` | yes | Schema relations written. | | `stats.dropped` | `integer` | yes | Warnings about constructs that were dropped: styling, links, activations, and so on. | | `stats.renamed` | `integer` | yes | Ids that were not valid Plotwork ids and were renamed. | | `validation` | `object \| null` | yes | The Plotwork validator run on `source`; null when nothing was converted. | | `validation.valid` | `boolean` | yes | True when the converted source has no Plotwork errors. It always is when `ok` is true. | | `validation.errors` | `Diagnostic[]` | yes | Plotwork errors in the converted source. Lines and columns point into `source`, not into the Mermaid. | | `validation.errors[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `validation.errors[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `validation.errors[].message` | `string` | yes | What is wrong, in one sentence. | | `validation.errors[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `validation.errors[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `validation.errors[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `validation.errors[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `validation.warnings` | `Diagnostic[]` | yes | Plotwork quality warnings, including icon and group-type suggestions with the exact icon= to write. Lines and columns point into `source`, not into the Mermaid. | | `validation.warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `validation.warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `validation.warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `validation.warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `validation.warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `validation.warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `validation.warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `validation.summary` | `string \| null` | yes | One-line description of the converted diagram, usable as alt text. | | `validation.size` | `object \| null` | yes | Natural size of the laid-out diagram. | | `validation.size.width` | `integer` | yes | Width in px. minimum 1. | | `validation.size.height` | `integer` | yes | Height in px. minimum 1. | | `quality` | `object` | no | How clean the converted diagram looks and up to three changes that would help (see draw_diagram); absent when it is not valid. | | `quality.score` | `integer` | yes | 0 to 100; 100 is a picture with nothing to fix. The same diagram always gets the same score. minimum 0; maximum 100. | | `quality.grade` | `"clean" \| "fair" \| "cluttered"` | yes | clean is 85 or more, fair 60 or more, cluttered below. | | `quality.measures` | `object` | yes | What the score is made from: crossings, lineOverBox, labelOverlaps, smallestTextPx (in an 880px column), aspectRatio, emptySpace (0 to 1), longestLine (px), detour (1 is straight), the counts of boxes, lines, labels, series, and slices, and any measure the kind adds. | | `quality.hints` | `string[]` | yes | Up to three changes, the one worth most first, each naming the statement or option to edit. | | `quality.line` | `string` | yes | The grade, the score, and the first hint in one line. | | `outline` | `string[]` | yes | The converted diagram as plain text, to check it says what the Mermaid said; empty when nothing was converted. | | `outlineTruncated` | `boolean` | yes | True when the summary level cut the outline short. Call again with detail "full" to read all of it. | | `renders` | `object[]` | yes | One picture per requested color scheme, light first; empty unless `render` was true and the conversion is valid, when svg is never, and when every picture was left out (see picturesOmitted). | | `renders[].theme` | `"light" \| "dark"` | yes | The color scheme this picture is drawn in. | | `renders[].svg` | `string` | yes | Self-contained SVG markup with an accessible title and description. | | `renders[].size` | `Size` | yes | Natural size of a rendered diagram in px. | | `renders[].size.width` | `integer` | yes | Width in px. minimum 1. | | `renders[].size.height` | `integer` | yes | Height in px. minimum 1. | | `picturesOmitted` | `object` | no | Present only when pictures were left out because the answer would have been too big for an AI client to read in one piece. | | `picturesOmitted.themes` | `("light" \| "dark")[]` | yes | The color schemes whose pictures were left out. | | `picturesOmitted.characters` | `integer` | yes | How many characters the pictures left out came to. | | `picturesOmitted.limit` | `integer` | yes | The most characters one answer may hold before the pictures are left out. | | `picturesOmitted.why` | `string` | yes | Why they were left out, and what to do. | | `viewUrl` | `string` | no | A link that shows a valid diagram in a browser; give it to the person who wants to look. The source is in the part after #, never sent to a server, but anyone with the link can read it. Absent when too long; next says so. | | `pngUrl` | `string` | no | The viewUrl with &download=png: opening it in a browser saves the diagram as a PNG for Slack, email, or a wiki. The PNG is drawn in the person's browser, not on a server, so it is a link to open, not an image address to embed. Absent when too long. | | `svgUrl` | `string` | no | The viewUrl with &download=svg: a person opens it to download the SVG. Not a file address for curl; use saveCommand. Absent when too long. | | `saveCommand` | `string` | no | Run in a shell to save the SVG into the repository: the CLI decodes the link locally, fetches nothing, and embeds the source. Set --out to the path; add --theme dark for dark. Absent when too long. | | `next` | `string` | yes | The next step in one sentence. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [`convert_mermaid`](https://docs.plotwork.dev/mcp/tools/convert-mermaid.md) | The same operation as an MCP tool | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Validate diagram > Page: https://docs.plotwork.dev/api/post-v1-diagrams-validate. Index of every page: https://docs.plotwork.dev/llms.txt Check diagram source. `POST /v1/diagrams/validate` ## TL;DR | Question | Answer | |---|---| | What does it do? | Check diagram source. | | Which tool does the same? | [`validate_diagram`](https://docs.plotwork.dev/mcp/tools/validate-diagram.md) | | What does it take? | A JSON body of type [`DiagramSourceInput`](https://docs.plotwork.dev/api/schemas.md#diagramsourceinput). | | What does it return? | Status 200 with a body of type [`ValidateResponse`](https://docs.plotwork.dev/api/schemas.md#validateresponse). | | What can go wrong? | `400` the body failed schema validation; `401` the api key is missing or not valid; `403` the account is on the free plan, which does not include the rest drawing and checking routes (plan.rest_not_included); `413` the body is larger than the size cap; `429` too many requests. Every failure uses the one error envelope. | ## What it does Parses the source and returns every error found in one pass, each with a line, column, and fix hint, plus quality warnings for a valid diagram. Invalid source is a normal 200 result with valid false, so callers can loop until it is valid. Nothing is rendered. ## Request body The body has the type [`DiagramSourceInput`](https://docs.plotwork.dev/api/schemas.md#diagramsourceinput). | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | Diagram source; the first line is the header, such as flow "Title" (draw_diagram lists every kind). at least 1 character; at most 50000 characters. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | Validation result. | [`ValidateResponse`](https://docs.plotwork.dev/api/schemas.md#validateresponse) | | 400 | The body failed schema validation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | The API key is missing or not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The account is on the Free plan, which does not include the REST drawing and checking routes (plan.rest_not_included). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than the size cap. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`ValidateResponse`](https://docs.plotwork.dev/api/schemas.md#validateresponse). | Field | Type | Required | Description | |---|---|---|---| | `valid` | `boolean` | yes | True when there are no errors; warnings do not make a diagram invalid. | | `kind` | `string \| null` | yes | The diagram kind, or null when the header could not be read. | | `errors` | `Diagnostic[]` | yes | Every error found in one pass, so they can be fixed together. | | `errors[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `errors[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `errors[].message` | `string` | yes | What is wrong, in one sentence. | | `errors[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `errors[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `errors[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `errors[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `warnings` | `Diagnostic[]` | yes | Quality warnings for a valid diagram, such as too dense or too wide. | | `warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `summary` | `string \| null` | yes | One-line description of the diagram, or null when invalid. | | `size` | `object \| null` | yes | Natural rendered size, or null when invalid. | | `size.width` | `integer` | yes | Width in px. minimum 1. | | `size.height` | `integer` | yes | Height in px. minimum 1. | | `quality` | `object` | no | How clean the laid-out diagram looks, with up to three changes; absent when invalid. | | `quality.score` | `integer` | yes | 0 to 100; 100 is a picture with nothing to fix. The same diagram always gets the same score. minimum 0; maximum 100. | | `quality.grade` | `"clean" \| "fair" \| "cluttered"` | yes | clean is 85 or more, fair 60 or more, cluttered below. | | `quality.measures` | `object` | yes | What the score is made from: crossings, lineOverBox, labelOverlaps, smallestTextPx (in an 880px column), aspectRatio, emptySpace (0 to 1), longestLine (px), detour (1 is straight), the counts of boxes, lines, labels, series, and slices, and any measure the kind adds. | | `quality.hints` | `string[]` | yes | Up to three changes, the one worth most first, each naming the statement or option to edit. | | `quality.line` | `string` | yes | The grade, the score, and the first hint in one line. | | `notices` | `Note[]` | no | Information notes, such as an icon alias resolved to a real icon; absent when there is none. | | `notices[].code` | `string` | yes | A stable code, such as I122 for an icon alias. | | `notices[].severity` | `"info"` | yes | A note never stops a drawing and is not a warning. | | `notices[].message` | `string` | yes | | | `notices[].line` | `integer` | yes | minimum 1. | | `notices[].column` | `integer` | yes | minimum 1. | | `notices[].length` | `integer` | yes | minimum 1. | | `notices[].hint` | `string \| null` | yes | The text to write instead. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [`validate_diagram`](https://docs.plotwork.dev/mcp/tools/validate-diagram.md) | The same operation as an MCP tool | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Render diagram > Page: https://docs.plotwork.dev/api/post-v1-diagrams-render. Index of every page: https://docs.plotwork.dev/llms.txt Render diagram source to SVG. `POST /v1/diagrams/render` ## TL;DR | Question | Answer | |---|---| | What does it do? | Render diagram source to SVG. | | Which tool does the same? | [`render_diagram`](https://docs.plotwork.dev/mcp/tools/render-diagram.md) | | What does it take? | A JSON body of type [`RenderInput`](https://docs.plotwork.dev/api/schemas.md#renderinput). | | What does it return? | Status 200 with a body of type [`RenderResponse`](https://docs.plotwork.dev/api/schemas.md#renderresponse). | | What can go wrong? | `400` the body failed schema validation; `401` the api key is missing or not valid; `403` the account is on the free plan, which does not include the rest drawing and checking routes (plan.rest_not_included); `413` the body is larger than the size cap; `422` the diagram source has errors; the diagnostics are in details; `429` too many requests. Every failure uses the one error envelope. | ## What it does Renders valid source to a self-contained SVG string in the light or dark theme and returns its pixel size and quality warnings. Source with errors is refused with 422 and the same diagnostics the validate route returns, in details. ## Request body The body has the type [`RenderInput`](https://docs.plotwork.dev/api/schemas.md#renderinput). | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | Diagram source; the first line is the header, such as flow "Title" (draw_diagram lists every kind). at least 1 character; at most 50000 characters. | | `theme` | `"light" \| "dark" \| "auto"` | no | Color scheme to paint with: light, dark, or auto for one picture that follows the reader's light or dark setting. default "light". | | `idPrefix` | `string` | no | Prefix for element ids; set it when inlining several diagrams into one page. at most 32 characters; pattern `^[A-Za-z][A-Za-z0-9_-]*$`. | | `embedSource` | `boolean` | no | Write the source into the SVG metadata so read_diagram can give it back. Set it true when you save the SVG into a repository; anyone with the file can then read the source. Default false. default false. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The rendered diagram. | [`RenderResponse`](https://docs.plotwork.dev/api/schemas.md#renderresponse) | | 400 | The body failed schema validation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | The API key is missing or not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The account is on the Free plan, which does not include the REST drawing and checking routes (plan.rest_not_included). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than the size cap. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 422 | The diagram source has errors; the diagnostics are in details. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`RenderResponse`](https://docs.plotwork.dev/api/schemas.md#renderresponse). | Field | Type | Required | Description | |---|---|---|---| | `svg` | `string` | yes | Self-contained SVG markup with an accessible title and description. | | `theme` | `"light" \| "dark" \| "auto"` | yes | The color scheme used. | | `size` | `Size` | yes | Natural size of a rendered diagram in px. | | `size.width` | `integer` | yes | Width in px. minimum 1. | | `size.height` | `integer` | yes | Height in px. minimum 1. | | `warnings` | `Diagnostic[]` | yes | | | `warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [`render_diagram`](https://docs.plotwork.dev/mcp/tools/render-diagram.md) | The same operation as an MCP tool | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Outline diagram > Page: https://docs.plotwork.dev/api/post-v1-diagrams-outline. Index of every page: https://docs.plotwork.dev/llms.txt Describe diagram source as text. `POST /v1/diagrams/outline` ## TL;DR | Question | Answer | |---|---| | What does it do? | Describe diagram source as text. | | Which tool does the same? | [`outline_diagram`](https://docs.plotwork.dev/mcp/tools/outline-diagram.md) | | What does it take? | A JSON body of type [`DiagramSourceInput`](https://docs.plotwork.dev/api/schemas.md#diagramsourceinput). | | What does it return? | Status 200 with a body of type [`OutlineResponse`](https://docs.plotwork.dev/api/schemas.md#outlineresponse). | | What can go wrong? | `400` the body failed schema validation; `401` the api key is missing or not valid; `403` the account is on the free plan, which does not include the rest drawing and checking routes (plan.rest_not_included); `413` the body is larger than the size cap; `422` the diagram source has errors; the diagnostics are in details; `429` too many requests. Every failure uses the one error envelope. | ## What it does Returns participants, steps, tables, and connections in reading order. Use it to check that a diagram says what was intended or as alt text. Source with errors is refused with 422. ## Request body The body has the type [`DiagramSourceInput`](https://docs.plotwork.dev/api/schemas.md#diagramsourceinput). | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | Diagram source; the first line is the header, such as flow "Title" (draw_diagram lists every kind). at least 1 character; at most 50000 characters. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The text outline. | [`OutlineResponse`](https://docs.plotwork.dev/api/schemas.md#outlineresponse) | | 400 | The body failed schema validation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | The API key is missing or not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The account is on the Free plan, which does not include the REST drawing and checking routes (plan.rest_not_included). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than the size cap. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 422 | The diagram source has errors; the diagnostics are in details. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`OutlineResponse`](https://docs.plotwork.dev/api/schemas.md#outlineresponse). | Field | Type | Required | Description | |---|---|---|---| | `kind` | `string` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `summary` | `string` | yes | One-line description. | | `outline` | `string[]` | yes | Lines in reading order: participants, steps, tables, and connections. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [`outline_diagram`](https://docs.plotwork.dev/mcp/tools/outline-diagram.md) | The same operation as an MCP tool | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Read diagram > Page: https://docs.plotwork.dev/api/post-v1-diagrams-read. Index of every page: https://docs.plotwork.dev/llms.txt Read the source back out of a Plotwork SVG. `POST /v1/diagrams/read` ## TL;DR | Question | Answer | |---|---| | What does it do? | Read the source back out of a Plotwork SVG. | | Which tool does the same? | [`read_diagram`](https://docs.plotwork.dev/mcp/tools/read-diagram.md) | | What does it take? | A JSON body of type [`ReadDiagramInput`](https://docs.plotwork.dev/api/schemas.md#readdiagraminput). | | What does it return? | Status 200 with a body of type [`ReadDiagramResponse`](https://docs.plotwork.dev/api/schemas.md#readdiagramresponse). | | What can go wrong? | `400` the body failed schema validation, or sent both of svg and url or neither; `401` the api key is missing or not valid; `403` the account is on the free plan, which does not include the rest drawing and checking routes (plan.rest_not_included); `413` the body is larger than the size cap; `429` too many requests. Every failure uses the one error envelope. | ## What it does Send the text of a Plotwork SVG or the link of a hosted one (cdn.plotwork.dev only). Returns the exact source the picture was drawn from, its kind, the stored diagram id, and a check of the source. A picture made without embedSource has no source: the answer says so (status no-source) and returns its title, outline, and labels. Problems with the text or the link are answers, not errors. ## Request body The body has the type [`ReadDiagramInput`](https://docs.plotwork.dev/api/schemas.md#readdiagraminput). | Field | Type | Required | Description | |---|---|---|---| | `svg` | `string` | no | The text of a Plotwork SVG: the content of an .svg file in the repository. Send this or url, not both. at least 1 character; at most 1000000 characters. | | `url` | `string` | no | The link of a hosted Plotwork picture, https://cdn.plotwork.dev/d/<22 characters>.svg. Only Plotwork's own picture hosts are fetched. Send this or svg, not both. at least 1 character; at most 200 characters. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The source, or why there is none. | [`ReadDiagramResponse`](https://docs.plotwork.dev/api/schemas.md#readdiagramresponse) | | 400 | The body failed schema validation, or sent both of svg and url or neither. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | The API key is missing or not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The account is on the Free plan, which does not include the REST drawing and checking routes (plan.rest_not_included). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than the size cap. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`ReadDiagramResponse`](https://docs.plotwork.dev/api/schemas.md#readdiagramresponse). | Field | Type | Required | Description | |---|---|---|---| | `status` | `"read" \| "no-source" \| "damaged" \| "not-a-picture" \| "refused" \| "unreachable"` | yes | read: the source was found. no-source: the picture carries none. damaged: it carries one that cannot be read back. not-a-picture: the text is not an SVG. refused: the link is not one this tool fetches. unreachable: the link could not be fetched. | | `hasSource` | `boolean` | yes | True when source is set. | | `source` | `string \| null` | yes | The exact source the picture was drawn from, ready to edit and send to draw_diagram; null when there is none. | | `kind` | `string \| null` | yes | The diagram kind of the source, or null. | | `id` | `string \| null` | yes | The id of the stored diagram when the picture was published (data-plotwork-id), or null. | | `from` | `"text" \| "url"` | yes | Where the picture came from: the text that was sent, or the link that was fetched. | | `validation` | `object \| null` | yes | The check of the source, or null when there is none. It parses the source; draw_diagram also lays it out and returns warnings. | | `validation.valid` | `boolean` | yes | True when the source has no errors. | | `validation.errors` | `Diagnostic[]` | yes | Every error, each with line, column, and a hint; empty when valid. | | `validation.errors[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `validation.errors[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `validation.errors[].message` | `string` | yes | What is wrong, in one sentence. | | `validation.errors[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `validation.errors[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `validation.errors[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `validation.errors[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `validation.summary` | `string \| null` | yes | One-line description of the diagram, or null when invalid. | | `picture` | `object \| null` | yes | What the picture says about itself, or null when the text is not an SVG. | | `picture.title` | `string \| null` | yes | The diagram title, or null. | | `picture.summary` | `string \| null` | yes | The one-line summary the picture carries, or null. | | `picture.theme` | `string \| null` | yes | The color scheme it was drawn in, or null. | | `picture.size` | `object \| null` | yes | Its size in px, or null. | | `picture.size.width` | `integer` | yes | Width in px. minimum 1. | | `picture.size.height` | `integer` | yes | Height in px. minimum 1. | | `picture.outline` | `string[]` | yes | The plain-language outline the picture carries, one entry for each line. Empty when the source was read: the source says more. | | `picture.labels` | `string[]` | yes | The text painted on the picture, each distinct label once, in painted order. Empty when the source was read. | | `message` | `string` | yes | What happened, in one sentence. | | `next` | `string` | yes | The next step in one sentence. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [`read_diagram`](https://docs.plotwork.dev/mcp/tools/read-diagram.md) | The same operation as an MCP tool | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Compare diagrams > Page: https://docs.plotwork.dev/api/post-v1-diagrams-compare. Index of every page: https://docs.plotwork.dev/llms.txt Say what changed between two versions of a diagram. `POST /v1/diagrams/compare` ## TL;DR | Question | Answer | |---|---| | What does it do? | Say what changed between two versions of a diagram. | | Which tool does the same? | [`compare_diagrams`](https://docs.plotwork.dev/mcp/tools/compare-diagrams.md) | | What does it take? | A JSON body of type [`CompareDiagramsInput`](https://docs.plotwork.dev/api/schemas.md#comparediagramsinput). | | What does it return? | Status 200 with a body of type [`CompareDiagramsResponse`](https://docs.plotwork.dev/api/schemas.md#comparediagramsresponse). | | What can go wrong? | `400` the body failed schema validation; `401` the api key is missing or not valid; `403` the account is on the free plan, which does not include the rest drawing and checking routes (plan.rest_not_included); `413` the body is larger than the size cap; `429` too many requests. Every failure uses the one error envelope. | ## What it does Each side is diagram source, the text of a Plotwork SVG that carries its source, or a hosted link on the picture hosts. Answers with every statement added, removed, renamed, moved to another block, or changed, matched by id rather than by line, with counts, a plain-language summary for a pull request review, and with markdown true a block ready to paste as a comment. A side that gives no source is an answer, not an error. Lays nothing out. ## Request body The body has the type [`CompareDiagramsInput`](https://docs.plotwork.dev/api/schemas.md#comparediagramsinput). | Field | Type | Required | Description | |---|---|---|---| | `before` | `string` | yes | The old diagram: source, the text of a Plotwork SVG that carries its source, or a cdn.plotwork.dev link. at least 1 character; at most 1000000 characters. | | `after` | `string` | yes | The new diagram, in any of the same three forms. at least 1 character; at most 1000000 characters. | | `markdown` | `boolean` | no | Also return a Markdown block ready to paste as a pull request comment. default false. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The changes, their counts, and the summary. | [`CompareDiagramsResponse`](https://docs.plotwork.dev/api/schemas.md#comparediagramsresponse) | | 400 | The body failed schema validation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | The API key is missing or not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The account is on the Free plan, which does not include the REST drawing and checking routes (plan.rest_not_included). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than the size cap. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`CompareDiagramsResponse`](https://docs.plotwork.dev/api/schemas.md#comparediagramsresponse). | Field | Type | Required | Description | |---|---|---|---| | `same` | `boolean` | yes | | | `before` | `object` | yes | | | `before.from` | `"source" \| "svg" \| "url"` | yes | | | `before.read` | `boolean` | yes | False when no source could be read from it; message says why. | | `before.kind` | `string \| null` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `before.valid` | `boolean` | yes | | | `before.message` | `string \| null` | yes | | | `after` | `object` | yes | | | `after.from` | `"source" \| "svg" \| "url"` | yes | | | `after.read` | `boolean` | yes | False when no source could be read from it; message says why. | | `after.kind` | `string \| null` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `after.valid` | `boolean` | yes | | | `after.message` | `string \| null` | yes | | | `counts` | `object` | yes | | | `counts.added` | `integer` | yes | | | `counts.removed` | `integer` | yes | | | `counts.renamed` | `integer` | yes | | | `counts.moved` | `integer` | yes | | | `counts.changed` | `integer` | yes | | | `changes` | `object[]` | yes | | | `changes[].change` | `"added" \| "removed" \| "renamed" \| "moved" \| "changed"` | yes | | | `changes[].subject` | `"header" \| "node" \| "group" \| "edge" \| "row" \| "series" \| "line"` | yes | | | `changes[].name` | `string` | yes | The id, the label in quotes, or for an edge its ends. | | `changes[].line` | `integer \| null` | yes | The line in after, or in before for a removal. | | `changes[].detail` | `string` | yes | | | `truncated` | `boolean` | yes | True when more changes were found than are listed; counts covers all of them. | | `summary` | `string` | yes | The changes in plain words, for a pull request review. | | `markdown` | `string \| null` | yes | | | `next` | `string` | yes | | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [`compare_diagrams`](https://docs.plotwork.dev/mcp/tools/compare-diagrams.md) | The same operation as an MCP tool | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Suggest diagram > Page: https://docs.plotwork.dev/api/post-v1-diagrams-suggest. Index of every page: https://docs.plotwork.dev/llms.txt Choose the kind of diagram for a request. `POST /v1/diagrams/suggest` ## TL;DR | Question | Answer | |---|---| | What does it do? | Choose the kind of diagram for a request. | | Which tool does the same? | [`suggest_diagram`](https://docs.plotwork.dev/mcp/tools/suggest-diagram.md) | | What does it take? | A JSON body of type [`SuggestDiagramInput`](https://docs.plotwork.dev/api/schemas.md#suggestdiagraminput). | | What does it return? | Status 200 with a body of type [`SuggestDiagramResponse`](https://docs.plotwork.dev/api/schemas.md#suggestdiagramresponse). | | What can go wrong? | `400` the body failed schema validation; `401` the api key is missing or not valid; `403` the account is on the free plan, which does not include the rest drawing and checking routes (plan.rest_not_included); `413` the body is larger than the size cap; `429` too many requests. Every failure uses the one error envelope. | ## What it does Takes a request in plain words, such as permission matrix for these endpoints and roles, and optional pasted data (a table, a list, or numbers by period), and answers with up to 3 diagram kinds, best first, each with a one-sentence reason, a confidence, the statements to use, and a short starter source that parses, built from the names and numbers it could read. A request that names no kind is a normal 200 result with low-confidence suggestions. Deterministic, and it lays nothing out. ## Request body The body has the type [`SuggestDiagramInput`](https://docs.plotwork.dev/api/schemas.md#suggestdiagraminput). | Field | Type | Required | Description | |---|---|---|---| | `request` | `string` | yes | What the picture is for, in plain words, such as "permission matrix for these endpoints and roles" or "how a refund moves between support and billing". at least 1 character; at most 1000 characters. | | `data` | `string` | no | Optional text to draw from: a pasted table, a list, numbers by period, or notes. Names and numbers in it are used in the starters. at most 20000 characters. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The kinds that fit, best first, each with a starter, and what to call next. | [`SuggestDiagramResponse`](https://docs.plotwork.dev/api/schemas.md#suggestdiagramresponse) | | 400 | The body failed schema validation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | The API key is missing or not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The account is on the Free plan, which does not include the REST drawing and checking routes (plan.rest_not_included). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than the size cap. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`SuggestDiagramResponse`](https://docs.plotwork.dev/api/schemas.md#suggestdiagramresponse). | Field | Type | Required | Description | |---|---|---|---| | `request` | `string` | yes | The request that was read. | | `suggestions` | `object[]` | yes | Up to 3, best first, one for each kind and style. | | `suggestions[].kind` | `string` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `suggestions[].style` | `string \| null` | yes | The style the starter uses: compare, score, raci, or risk for a matrix, files or org for a tree; null for the kind's only style. | | `suggestions[].why` | `string` | yes | One sentence: why this kind fits. | | `suggestions[].confidence` | `"high" \| "medium" \| "low"` | yes | high: the text names this kind of picture or its shape fits it exactly. medium: it leans toward it. low: a fallback worth considering. | | `suggestions[].starter` | `string` | yes | A short Plotwork source of this kind that parses without errors: built from the names and numbers in the text where it could find them, otherwise a small example to edit. Replace every placeholder before you draw it. | | `suggestions[].tailored` | `boolean` | yes | True when the starter uses names or numbers from the text; false when it is the kind's small example. | | `suggestions[].statements` | `string[]` | yes | The few statements of this kind to write, as syntax. get_diagram_syntax with this kind has them all. | | `importFormat` | `string` | no | Present when the data is, or the request names, a file import_source reads (sql, openapi, compose, terraform, and so on): call import_source with this format and the file's text instead of editing a starter. | | `next` | `string` | yes | The next step in one sentence. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [`suggest_diagram`](https://docs.plotwork.dev/mcp/tools/suggest-diagram.md) | The same operation as an MCP tool | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Import source > Page: https://docs.plotwork.dev/api/post-v1-import. Index of every page: https://docs.plotwork.dev/llms.txt Turn a file of another format into a diagram. `POST /v1/import` ## TL;DR | Question | Answer | |---|---| | What does it do? | Turn a file of another format into a diagram. | | Which tool does the same? | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | | What does it take? | A JSON body of type [`ImportSourceInput`](https://docs.plotwork.dev/api/schemas.md#importsourceinput). | | What does it return? | Status 200 with a body of type [`ImportSourceResponse`](https://docs.plotwork.dev/api/schemas.md#importsourceresponse). | | What can go wrong? | `400` the body failed schema validation; `401` the api key is missing or not valid; `403` the account is on the free plan, which does not include the rest drawing and checking routes (plan.rest_not_included); `413` the body is larger than the size cap; `429` too many requests. Every failure uses the one error envelope. | ## What it does Takes the text of a file and its format: SQL DDL, Prisma, or Drizzle becomes a schema; an OpenAPI 3 document becomes a matrix of who may call each endpoint, or one operation as a sequence; Terraform, Bicep, an ARM template, Pulumi, CloudFormation, the AWS CDK, Docker Compose, Kubernetes manifests, Helm, Kustomize, a Backstage catalog, AsyncAPI documents, or a GitHub Actions, GitLab CI/CD, or Azure Pipelines pipeline becomes a flow with icons and frames; a Structurizr workspace becomes a C4 diagram of one view; CSV or JSON data becomes the chart its shape suits; tree output or a list of paths becomes a tree; a git log becomes a git graph or a timeline. Answers with the Plotwork source, a diagnostic for everything left out with its line and column in the file, the validation, and an outline. A file that cannot be read is a normal 200 result with ok false. It runs no code and fetches nothing, and the text is not stored. ## Request body The body has the type [`ImportSourceInput`](https://docs.plotwork.dev/api/schemas.md#importsourceinput). | Field | Type | Required | Description | |---|---|---|---| | `format` | `"sql" \| "prisma" \| "drizzle" \| "openapi" \| "terraform" \| "terraform-plan" \| "bicep" \| "arm" \| "pulumi" \| "cloudformation" \| "cdk" \| "compose" \| "kubernetes" \| "helm" \| "kustomize" \| "backstage" \| "structurizr" \| "asyncapi" \| "github-actions" \| "gitlab-ci" \| "azure-pipelines" \| "csv" \| "json" \| "tree" \| "git-log"` | yes | What the text is; get_diagram_syntax with topic import-formats describes each. | | `text` | `string` | no | The file content, as text. Send text or files, not both. | | `files` | `object[]` | no | The files of a folder, for a format that spans files; the others read the first. | | `files[].path` | `string` | yes | The file path, such as infra/network.tf; diagnostics name it. at least 1 character; at most 300 characters. | | `files[].text` | `string` | yes | The file content, as text. | | `title` | `string` | no | The diagram title; taken from the file when left out. at most 200 characters. | | `partsPage` | `integer` | no | Which page of parts to return when a split import has more than 24 diagrams: 1 (default) is the first 24. minimum 1; maximum 100000. | | `options` | `object` | no | default {}. | | `options.view` | `"endpoints" \| "map" \| "sequence" \| "git" \| "timeline" \| "context" \| "container" \| "component"` | no | The picture of a format drawn several ways. | | `options.operationId` | `string` | no | openapi sequence: the operation. at most 200 characters. | | `options.chart` | `"auto" \| "xychart" \| "pie" \| "sankey" \| "treemap" \| "matrix"` | no | csv, json: the chart; auto picks. | | `options.tables` | `string[]` | no | Tables to draw, by name or prefix*. | | `options.paths` | `string[]` | no | openapi: path prefixes to read. | | `options.tags` | `string[]` | no | openapi: tags to read. | | `options.varFiles` | `string[]` | no | Variables files to apply. | | `options.groupBy` | `"module" \| "network" \| "file" \| "owner"` | no | How things are framed; module is the default. | | `options.root` | `string` | no | The root, stack, or template of several to draw. at most 300 characters. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The imported source, its diagnostics, its check, and its outline. | [`ImportSourceResponse`](https://docs.plotwork.dev/api/schemas.md#importsourceresponse) | | 400 | The body failed schema validation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | The API key is missing or not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The account is on the Free plan, which does not include the REST drawing and checking routes (plan.rest_not_included). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than the size cap. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`ImportSourceResponse`](https://docs.plotwork.dev/api/schemas.md#importsourceresponse). | Field | Type | Required | Description | |---|---|---|---| | `ok` | `boolean` | yes | True when `source` is valid Plotwork source. False means nothing was imported and `source` is empty: read `diagnostics`. | | `kind` | `string \| null` | yes | The kind of diagram the file became, or null when nothing was imported. | | `format` | `string` | yes | The format the text was read as. | | `source` | `string` | yes | The Plotwork source, or an empty string when nothing could be imported. | | `diagnostics` | `Diagnostic[]` | yes | Everything left out, changed, or wrong, with a hint. Codes start with I and point into the text you sent: I1xx errors stopped the import, I2xx warnings did not. | | `diagnostics[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `diagnostics[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `diagnostics[].message` | `string` | yes | What is wrong, in one sentence. | | `diagnostics[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `diagnostics[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `diagnostics[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `diagnostics[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `stats` | `object` | yes | | | `stats.items` | `integer` | yes | Boxes, tables, rows, slices, places, entries, or commits written. | | `stats.links` | `integer` | yes | Arrows, relations, messages, flows, or merges written. | | `stats.groups` | `integer` | yes | Frames, sections, or folders written. | | `stats.dropped` | `integer` | yes | Things in the file left out by a cap or because the picture has no place for them; each is in diagnostics. | | `coverage` | `object \| null` | yes | How much of the file the picture shows; null when nothing was imported. When complete is false, the picture is not the whole file: say so. | | `coverage.noun` | `string` | yes | What was counted, such as resources, services, tables, or endpoints. | | `coverage.total` | `integer` | yes | Everything of that kind in the file. | | `coverage.drawn` | `integer` | yes | Drawn as a box, frame, table, row, or entry of its own. | | `coverage.summarised` | `integer` | yes | Not drawn on their own but counted on the box they belong to, such as "3 role assignments". | | `coverage.omitted` | `integer` | yes | Not in the picture at all; the diagnostics say which and why. | | `coverage.complete` | `boolean` | yes | True when nothing was omitted. | | `coverage.line` | `string` | yes | The coverage in one sentence, such as "Drawn 12 of 85 resources; 60 are counted on the boxes they belong to and 13 are left out.". | | `validation` | `object \| null` | yes | The Plotwork validator run on `source`; null when nothing was imported. | | `validation.valid` | `boolean` | yes | True when the source has no Plotwork errors. It always is when `ok` is true. | | `validation.errors` | `Diagnostic[]` | yes | Plotwork errors. Lines and columns point into `source`. | | `validation.errors[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `validation.errors[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `validation.errors[].message` | `string` | yes | What is wrong, in one sentence. | | `validation.errors[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `validation.errors[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `validation.errors[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `validation.errors[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `validation.warnings` | `Diagnostic[]` | yes | Plotwork quality warnings, with the exact fix in each hint. Lines and columns point into `source`. | | `validation.warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `validation.warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `validation.warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `validation.warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `validation.warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `validation.warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `validation.warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `validation.summary` | `string \| null` | yes | One-line description of the diagram, usable as alt text. | | `validation.size` | `object \| null` | yes | Natural size of the laid-out diagram. | | `validation.size.width` | `integer` | yes | Width in px. minimum 1. | | `validation.size.height` | `integer` | yes | Height in px. minimum 1. | | `parts` | `object[]` | yes | When the file is too big for one readable picture, the diagrams it was split into, in order, at most 24 a page; `source` is the first diagram of all. Show or draw each of them. Empty when the import is one diagram. | | `parts[].title` | `string` | yes | What this diagram shows, such as one resource group or one tag of an API. | | `parts[].kind` | `string` | yes | The kind of diagram. | | `parts[].source` | `string` | yes | The Plotwork source of this diagram; draw it with draw_diagram. | | `parts[].items` | `integer` | yes | The boxes, rows, or entries it draws. | | `parts[].valid` | `boolean` | yes | True when the source has no Plotwork errors. | | `parts[].warnings` | `Diagnostic[]` | yes | Plotwork quality warnings of this diagram. Lines and columns point into its source. | | `parts[].warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `parts[].warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `parts[].warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `parts[].warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `parts[].warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `parts[].warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `parts[].warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `partsTotal` | `integer` | yes | How many diagrams the import was split into in all; 0 when it is one diagram. | | `partsPage` | `integer` | yes | The page of parts this answer holds, from 1. | | `partsPages` | `integer` | yes | How many pages of parts there are; call again with partsPage for the next one. | | `outline` | `string[]` | yes | The diagram as plain text, to check it says what the file says; empty when nothing was imported. | | `outlineTruncated` | `boolean` | yes | True when the outline was cut short at its summary length. | | `viewUrl` | `string` | no | A link that shows a valid diagram in a browser; give it to the person who wants to look. The source is in the part after #, never sent to a server, but anyone with the link can read it. Absent when too long; next says so. | | `pngUrl` | `string` | no | The viewUrl with &download=png: opening it in a browser saves the diagram as a PNG for Slack, email, or a wiki. The PNG is drawn in the person's browser, not on a server, so it is a link to open, not an image address to embed. Absent when too long. | | `svgUrl` | `string` | no | The viewUrl with &download=svg: a person opens it to download the SVG. Not a file address for curl; use saveCommand. Absent when too long. | | `saveCommand` | `string` | no | Run in a shell to save the SVG into the repository: the CLI decodes the link locally, fetches nothing, and embeds the source. Set --out to the path; add --theme dark for dark. Absent when too long. | | `next` | `string` | yes | The next step in one sentence. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [`import_source`](https://docs.plotwork.dev/mcp/tools/import-source.md) | The same operation as an MCP tool | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Map icons > Page: https://docs.plotwork.dev/api/post-v1-icons-map. Index of every page: https://docs.plotwork.dev/llms.txt Choose icons for many names at once. `POST /v1/icons/map` ## TL;DR | Question | Answer | |---|---| | What does it do? | Choose icons for many names at once. | | Which tool does the same? | [`map_icons`](https://docs.plotwork.dev/mcp/tools/map-icons.md) | | What does it take? | A JSON body of type [`MapIconsInput`](https://docs.plotwork.dev/api/schemas.md#mapiconsinput). | | What does it return? | Status 200 with a body of type [`MapIconsResponse`](https://docs.plotwork.dev/api/schemas.md#mapiconsresponse). | | What can go wrong? | `400` the body failed schema validation; `401` the api key is missing or not valid; `403` the account is on the free plan, which does not include the rest drawing and checking routes (plan.rest_not_included); `413` the body is larger than the size cap; `429` too many requests. Every failure uses the one error envelope. | ## What it does Takes up to 40 service or product names and an optional cloud provider, and answers for each name with one icon to write as icon=, or, when the name could mean more than one icon, the candidates to choose from. It never guesses: a name that matches nothing is listed as a gap with a neutral fallback. A name with no match is a normal 200 result, never an error. Prefer this route over calling the icon search route once per name; use the search route when you are exploring one idea. ## Request body The body has the type [`MapIconsInput`](https://docs.plotwork.dev/api/schemas.md#mapiconsinput). | Field | Type | Required | Description | |---|---|---|---| | `names` | `string[]` | yes | Services, products, or ideas to find icons for, written as the user wrote them ("S3", "Postgres", "load balancer"). Up to 40 names of up to 80 characters. | | `provider` | `"aws" \| "gcp" \| "azure" \| "alibaba" \| "oci" \| "ibm" \| "cloudflare" \| "digitalocean" \| "vercel"` | no | The cloud the architecture runs on. A name is looked up there first, so "queue" finds aws:sqs for aws. Omit it when the names are not tied to one provider. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | One icon decision per name, and the names that found no icon. | [`MapIconsResponse`](https://docs.plotwork.dev/api/schemas.md#mapiconsresponse) | | 400 | The body failed schema validation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | The API key is missing or not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The account is on the Free plan, which does not include the REST drawing and checking routes (plan.rest_not_included). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than the size cap. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`MapIconsResponse`](https://docs.plotwork.dev/api/schemas.md#mapiconsresponse). | Field | Type | Required | Description | |---|---|---|---| | `provider` | `"aws" \| "gcp" \| "azure" \| "alibaba" \| "oci" \| "ibm" \| "cloudflare" \| "digitalocean" \| "vercel" \| null` | yes | The provider hint that was used, or null. | | `results` | `object[]` | yes | One entry per name that matched something, in the order sent. | | `results[].name` | `string` | yes | The name exactly as it was sent. | | `results[].resolved` | `boolean` | yes | True when icon holds the one icon to use. False when the name could mean more than one: choose from candidates, or ask the user. | | `results[].icon` | `string \| null` | yes | The icon to write as icon=, such as aws:lambda; null when resolved is false. | | `results[].confidence` | `"exact" \| "strong" \| "ambiguous"` | yes | exact: the name is the icon or product name. strong: the name clearly points at this icon and no other provider competes. ambiguous: several icons fit, or the match is by idea only. | | `results[].alternatives` | `string[]` | yes | Up to 3 other icons that also fit a resolved name, best first. Empty when none. | | `results[].candidates` | `string[]` | yes | When resolved is false, the icons that fit, best first (up to 4). Empty when resolved. | | `gaps` | `object[]` | yes | Names that matched nothing, in the order sent. | | `gaps[].name` | `string` | yes | The name that matched no icon. | | `gaps[].fallback` | `string` | yes | A neutral icon to use instead, or leave icon= out so the node type draws its own. | | `gaps[].reason` | `string` | yes | Why there is no match, and what to try. | | `resolvedCount` | `integer` | yes | How many names have one icon to use. | | `ambiguousCount` | `integer` | yes | How many names need a choice from candidates. | | `gapCount` | `integer` | yes | How many names matched nothing. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [`map_icons`](https://docs.plotwork.dev/mcp/tools/map-icons.md) | The same operation as an MCP tool | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Search icons > Page: https://docs.plotwork.dev/api/get-v1-icons. Index of every page: https://docs.plotwork.dev/llms.txt Find bundled icon names. `GET /v1/icons` ## TL;DR | Question | Answer | |---|---| | What does it do? | Find bundled icon names. | | Which tool does the same? | [`search_icons`](https://docs.plotwork.dev/mcp/tools/search-icons.md) | | What does it take? | `q`, `limit` in the query string. | | What does it return? | Status 200 with a body of type [`IconSearchResponse`](https://docs.plotwork.dev/api/schemas.md#iconsearchresponse). | | What can go wrong? | `400` the query failed schema validation; `401` the api key is missing or not valid; `403` the account is on the free plan, which does not include the rest drawing and checking routes (plan.rest_not_included); `429` too many requests. Every failure uses the one error envelope. | ## What it does Returns set:name references such as lucide:database, simple:postgresql, or aws:lambda, best match first. It understands product names and phrases, so lambda, s3, gke, or serverless function each find the right provider icon. Unknown icon names are rejected when a diagram is validated. ## Query parameters | Field | Type | Required | Description | |---|---|---|---| | `q` | `string` | yes | Words to look for: a name such as "database", an idea such as "token exchange", or a cloud product such as "lambda" or "gke". | | `limit` | `integer` | no | Most names to return. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | Matching names. | [`IconSearchResponse`](https://docs.plotwork.dev/api/schemas.md#iconsearchresponse) | | 400 | The query failed schema validation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | The API key is missing or not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The account is on the Free plan, which does not include the REST drawing and checking routes (plan.rest_not_included). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`IconSearchResponse`](https://docs.plotwork.dev/api/schemas.md#iconsearchresponse). | Field | Type | Required | Description | |---|---|---|---| | `query` | `string` | yes | The query that was searched. | | `items` | `string[]` | yes | Matching icon references as set:name, best match first. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [`search_icons`](https://docs.plotwork.dev/mcp/tools/search-icons.md) | The same operation as an MCP tool | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Sync Markdown > Page: https://docs.plotwork.dev/api/post-v1-markdown-sync. Index of every page: https://docs.plotwork.dev/llms.txt Turn a document's diagram fences into pictures. `POST /v1/markdown/sync` ## TL;DR | Question | Answer | |---|---| | What does it do? | Turn a document's diagram fences into pictures. | | Which tool does the same? | [`sync_markdown`](https://docs.plotwork.dev/mcp/tools/sync-markdown.md) | | What does it take? | A JSON body of type [`SyncMarkdownInput`](https://docs.plotwork.dev/api/schemas.md#syncmarkdowninput). | | What does it return? | Status 200 with a body of type [`SyncMarkdownResponse`](https://docs.plotwork.dev/api/schemas.md#syncmarkdownresponse). | | What can go wrong? | `400` the body failed schema validation; `401` the api key is missing or not valid; `403` the account is on the free plan, which does not include the rest drawing and checking routes (plan.rest_not_included); `413` the body is larger than the size cap; `429` too many requests. Every failure uses the one error envelope. | ## What it does Takes a whole Markdown document and its path and answers with the rewritten document (every diagram fence wrapped in a collapsed source section with its picture and a text description), the SVG files to write, and a report per fence with errors that carry line numbers counted in the document sent. With convertMermaid set, every Mermaid fence that can be converted becomes a diagram fence first; the others are left exactly as they were and reported. The route reads and writes no file: the caller writes the returned document and files. A fence with errors is a normal 200 result with status failed in the report. Mode check changes nothing and only reports. Prefer this route over converting, validating, and rendering each fence yourself. ## Request body The body has the type [`SyncMarkdownInput`](https://docs.plotwork.dev/api/schemas.md#syncmarkdowninput). | Field | Type | Required | Description | |---|---|---|---| | `markdown` | `string` | yes | The whole Markdown document, exactly as it is on disk, with its line endings. Up to 10000000 characters. at least 1 character; at most 10000000 characters. | | `documentPath` | `string` | yes | Where the document lives, relative to the repository root, with forward slashes, for example docs/guide.md. Picture links and file paths are worked out from it. at least 1 character; at most 300 characters. | | `assetsDir` | `string` | no | The directory for the SVG files, relative to the repository root. The default is assets/diagrams next to the document. at least 1 character; at most 300 characters. | | `prefix` | `string` | no | Text put in front of every picture file name, for documents that share one assets directory. By default a titled diagram is named from its title and an untitled one from the document name. at least 1 character; at most 32 characters. | | `convertMermaid` | `boolean` | no | When true, every ```mermaid fence that can be converted is replaced by a ```diagram fence first, then drawn. Fences that cannot be converted are left exactly as they were and reported. default false. | | `mode` | `"sync" \| "check"` | no | sync (the default) returns the rewritten document and the files to write. check changes nothing: it only reports whether a picture is stale or a fence would convert. default "sync". | | `files` | `"inline" \| "none"` | no | inline (the default) returns each SVG file with its content. none returns each with path, bytes, sha256, and viewUrl instead, so the answer stays small. default "inline". | | `page` | `integer` | no | Which page of files to return, from 1; pictures are paged in document order, light and dark together. minimum 1. | | `pageSize` | `integer` | no | Pictures per page, at most 30. Setting page or pageSize turns paging on. minimum 1; maximum 30. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The rewritten document, the files to write, and the report. | [`SyncMarkdownResponse`](https://docs.plotwork.dev/api/schemas.md#syncmarkdownresponse) | | 400 | The body failed schema validation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | The API key is missing or not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The account is on the Free plan, which does not include the REST drawing and checking routes (plan.rest_not_included). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than the size cap. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`SyncMarkdownResponse`](https://docs.plotwork.dev/api/schemas.md#syncmarkdownresponse). | Field | Type | Required | Description | |---|---|---|---| | `mode` | `"sync" \| "check"` | yes | The mode that ran. | | `markdown` | `string` | yes | The document to write back: every diagram fence wrapped in its collapsed source section, with the picture block and text description under it, and everything else unchanged. In check mode this is the document that was sent. | | `changed` | `boolean` | yes | True when the document or a file differs from what was sent. In check mode this says the pictures are stale or a fence would convert. | | `files` | `object[]` | yes | The SVG files to write, light and dark for each picture that was refreshed, with paths relative to the repository root. Empty in check mode. A picture that was already current has no files. | | `files[].path` | `string` | yes | Where to write the file, relative to the repository root. | | `files[].content` | `string` | no | The file contents; absent when files is none. | | `files[].encoding` | `"utf8"` | yes | How content is encoded. Always utf8: the files are SVG text. | | `files[].bytes` | `integer` | no | Only when files is none: the size of the file in bytes. | | `files[].sha256` | `string` | no | Only when files is none: the SHA-256 of the content, in hex, to check a file fetched later. | | `files[].viewUrl` | `string` | no | Only when files is none: a link that shows this diagram in a browser. | | `report` | `object[]` | yes | One entry per fence handled, in document order. | | `report[].id` | `string \| null` | yes | The picture id, which names the SVG files; null for a fence that has none (a mermaid fence that was not converted). | | `report[].source` | `"diagram" \| "mermaid"` | yes | diagram for a fence that was already Plotwork, mermaid for one that came from Mermaid. | | `report[].kind` | `string \| null` | yes | The diagram kind, or null when it could not be read. | | `report[].line` | `integer` | yes | The 1-based line of the opening fence in the document that was sent. minimum 1. | | `report[].status` | `"synced" \| "converted" \| "unchanged" \| "skipped" \| "failed"` | yes | synced: the picture was written or refreshed. converted: a mermaid fence became a diagram fence and was drawn. unchanged: the picture was already current. skipped: a cap stopped it, see message. failed: it has errors, the fence is left as it was. | | `report[].diagnostics` | `Diagnostic[]` | yes | Errors and warnings, with line numbers counted in the document that was sent. For a converted fence they point at the Mermaid lines. | | `report[].diagnostics[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `report[].diagnostics[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `report[].diagnostics[].message` | `string` | yes | What is wrong, in one sentence. | | `report[].diagnostics[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `report[].diagnostics[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `report[].diagnostics[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `report[].diagnostics[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `report[].message` | `string \| null` | yes | A plain sentence when something needs saying, such as why a fence was skipped; otherwise null. | | `summary` | `object` | yes | | | `summary.diagrams` | `integer` | yes | Fences handled: synced, converted, unchanged, skipped, and failed together. | | `summary.synced` | `integer` | yes | | | `summary.converted` | `integer` | yes | | | `summary.unchanged` | `integer` | yes | | | `summary.skipped` | `integer` | yes | | | `summary.failed` | `integer` | yes | | | `summary.mermaidIgnored` | `integer` | yes | Mermaid fences left alone because convertMermaid was false. | | `summary.files` | `integer` | yes | Files to write. | | `summary.outputBytes` | `integer` | yes | Bytes in the returned document and files together. | | `caps` | `object` | yes | | | `caps.hit` | `boolean` | yes | True when a cap stopped some of the work. Nothing is cut silently: see messages. | | `caps.messages` | `string[]` | yes | One plain sentence per cap that was hit, saying what was skipped and what to do. | | `caps.maxDiagrams` | `integer` | yes | Most fences handled in one call. | | `caps.maxOutputBytes` | `integer` | yes | Most bytes in the returned document and files together. | | `pages` | `object` | no | Present when page or pageSize was sent: which pictures files holds, and the next page. | | `pages.page` | `integer` | yes | | | `pages.pageSize` | `integer` | yes | | | `pages.pages` | `integer` | yes | Pages in all. | | `pages.pictures` | `integer` | yes | Pictures in all, each a light and a dark file. | | `pages.nextPage` | `integer \| null` | yes | The page to ask for next, or null on the last one. | | `next` | `string` | yes | The next step in one sentence. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [`sync_markdown`](https://docs.plotwork.dev/mcp/tools/sync-markdown.md) | The same operation as an MCP tool | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Plan document diagrams > Page: https://docs.plotwork.dev/api/post-v1-markdown-plan. Index of every page: https://docs.plotwork.dev/llms.txt Find the sections of a document a picture would help. `POST /v1/markdown/plan` ## TL;DR | Question | Answer | |---|---| | What does it do? | Find the sections of a document a picture would help. | | Which tool does the same? | [`plan_document_diagrams`](https://docs.plotwork.dev/mcp/tools/plan-document-diagrams.md) | | What does it take? | A JSON body of type [`PlanDocumentInput`](https://docs.plotwork.dev/api/schemas.md#plandocumentinput). | | What does it return? | Status 200 with a body of type [`PlanDocumentResponse`](https://docs.plotwork.dev/api/schemas.md#plandocumentresponse). | | What can go wrong? | `400` the body failed schema validation; `401` the api key is missing or not valid; `403` the account is on the free plan, which does not include the rest drawing and checking routes (plan.rest_not_included); `413` the body is larger than the size cap; `429` too many requests. Every failure uses the one error envelope. | ## What it does Takes a whole document (Markdown, HTML, or plain text pasted from a wiki) and answers with the sections a picture would help, in document order: the heading and its line, the diagram kind, a one-sentence reason, a confidence, and a starter source drafted from the section's own lists, tables, numbers, and dates. Sections that read well as text, or already hold a diagram fence, are left out, and every cap that left something out is stated in caps. Draws nothing, writes no file, and lays nothing out. ## Request body The body has the type [`PlanDocumentInput`](https://docs.plotwork.dev/api/schemas.md#plandocumentinput). | Field | Type | Required | Description | |---|---|---|---| | `document` | `string` | yes | The whole document: Markdown, plain text pasted from Confluence, Notion, or Google Docs, or HTML. Up to 10000000 characters. at least 1 character; at most 10000000 characters. | | `maxSuggestions` | `integer` | no | Most sections to suggest a picture for, up to 12. The strongest are kept, in document order. The default is 8. minimum 1; maximum 12; default 8. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The sections a picture would help, each with a starter, and what to do next. | [`PlanDocumentResponse`](https://docs.plotwork.dev/api/schemas.md#plandocumentresponse) | | 400 | The body failed schema validation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | The API key is missing or not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The account is on the Free plan, which does not include the REST drawing and checking routes (plan.rest_not_included). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than the size cap. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`PlanDocumentResponse`](https://docs.plotwork.dev/api/schemas.md#plandocumentresponse). | Field | Type | Required | Description | |---|---|---|---| | `summary` | `string` | yes | One sentence: how many sections were read and how many would gain a picture. | | `format` | `"markdown" \| "html" \| "text"` | yes | How the document was read: Markdown headings, HTML h1 to h6, or plain text with heading-like lines. | | `sectionsRead` | `integer` | yes | Sections read, up to the cap. | | `suggestions` | `object[]` | yes | In document order. Sections that read well as text, or already have a diagram, are not listed. | | `suggestions[].heading` | `string` | yes | The heading of the section, as written. | | `suggestions[].line` | `integer` | yes | The 1-based line of the heading in the document that was sent. minimum 1. | | `suggestions[].kind` | `string` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `suggestions[].style` | `string \| null` | yes | The style the starter uses: compare, score, raci, or risk for a matrix, files or org for a tree; null for the kind's only style. | | `suggestions[].why` | `string` | yes | One sentence: what in the section points to this kind. | | `suggestions[].confidence` | `"high" \| "medium" \| "low"` | yes | high: the text names this kind of picture or its shape fits it exactly. medium: it leans toward it. low: a fallback worth considering. | | `suggestions[].starter` | `string` | yes | A short Plotwork source of this kind that parses without errors: built from the names and numbers in the text where it could find them, otherwise a small example to edit. Replace every placeholder before you draw it. | | `suggestions[].tailored` | `boolean` | yes | True when the starter uses names or numbers from the text; false when it is the kind's small example. | | `caps` | `object` | yes | | | `caps.hit` | `boolean` | yes | True when a cap left something out; messages says what. | | `caps.messages` | `string[]` | yes | | | `caps.maxSections` | `integer` | yes | | | `caps.maxSuggestions` | `integer` | yes | | | `next` | `string` | yes | The next step in one sentence. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [`plan_document_diagrams`](https://docs.plotwork.dev/mcp/tools/plan-document-diagrams.md) | The same operation as an MCP tool | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Find pattern > Page: https://docs.plotwork.dev/api/post-v1-patterns-find. Index of every page: https://docs.plotwork.dev/llms.txt Find a ready-made architecture diagram. `POST /v1/patterns/find` ## TL;DR | Question | Answer | |---|---| | What does it do? | Find a ready-made architecture diagram. | | Which tool does the same? | [`find_pattern`](https://docs.plotwork.dev/mcp/tools/find-pattern.md) | | What does it take? | A JSON body of type [`FindPatternInput`](https://docs.plotwork.dev/api/schemas.md#findpatterninput). | | What does it return? | Status 200 with a body of type [`FindPatternResponse`](https://docs.plotwork.dev/api/schemas.md#findpatternresponse). | | What can go wrong? | `400` the body failed schema validation; `401` the api key is missing or not valid; `403` the account is on the free plan, which does not include the rest drawing and checking routes (plan.rest_not_included); `413` the body is larger than the size cap; `429` too many requests. Every failure uses the one error envelope. | ## What it does Takes a request in plain words, such as serverless api or two availability zones, and optionally a provider flavour, tags, a diagram kind, and a limit of up to 5. Answers with the best matches first, each with an id, a one-sentence summary, its tags, and the flavours it comes in, and never the source, so a search stays small. Nothing that fits is a normal 200 result with no matches and a hint for what to try next, never an error. Start here when a request names a common architecture, then read the pattern with the get route or draw it with the draw route. ## Request body The body has the type [`FindPatternInput`](https://docs.plotwork.dev/api/schemas.md#findpatterninput). | Field | Type | Required | Description | |---|---|---|---| | `query` | `string` | yes | The architecture wanted, in plain words, such as "three tier network". at least 1 character; at most 200 characters. | | `provider` | `"neutral" \| "aws" \| "azure" \| "gcp"` | no | Only patterns that come in this flavour: neutral, aws, azure, or gcp. | | `tags` | `string[]` | no | Tags every result must have, such as "load balancer". Up to 5. | | `kind` | `"flow" \| "sequence" \| "schema" \| "git" \| "gantt" \| "class" \| "state" \| "timeline" \| "journey" \| "mindmap" \| "treemap" \| "pie" \| "quadrant" \| "layers" \| "c4" \| "matrix" \| "sankey" \| "xychart" \| "tree" \| "swimlane"` | no | Only patterns of this diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane. | | `limit` | `integer` | no | Most results to return, up to 5. minimum 1; maximum 5; default 5. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The matching patterns, best first, and what to call next. | [`FindPatternResponse`](https://docs.plotwork.dev/api/schemas.md#findpatternresponse) | | 400 | The body failed schema validation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | The API key is missing or not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The account is on the Free plan, which does not include the REST drawing and checking routes (plan.rest_not_included). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than the size cap. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`FindPatternResponse`](https://docs.plotwork.dev/api/schemas.md#findpatternresponse). | Field | Type | Required | Description | |---|---|---|---| | `query` | `string` | yes | The query that was searched. | | `matches` | `object[]` | yes | Up to the limit, best match first. Empty when nothing fits. | | `matches[].id` | `string` | yes | The pattern id, as find_pattern returns it, such as three-tier-network. at least 1 character; at most 60 characters. | | `matches[].title` | `string` | yes | | | `matches[].summary` | `string` | yes | One sentence: what the diagram shows. | | `matches[].kind` | `string` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `matches[].tags` | `string[]` | yes | | | `matches[].providers` | `("neutral" \| "aws" \| "azure" \| "gcp")[]` | yes | The flavours the pattern comes in. | | `matches[].score` | `number` | yes | How well it matches; only the order of the results is meaningful. | | `next` | `string` | yes | The next step in one sentence. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [`find_pattern`](https://docs.plotwork.dev/mcp/tools/find-pattern.md) | The same operation as an MCP tool | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Get pattern > Page: https://docs.plotwork.dev/api/post-v1-patterns-get. Index of every page: https://docs.plotwork.dev/llms.txt Read a pattern filled for a flavour. `POST /v1/patterns/get` ## TL;DR | Question | Answer | |---|---| | What does it do? | Read a pattern filled for a flavour. | | Which tool does the same? | [`get_pattern`](https://docs.plotwork.dev/mcp/tools/get-pattern.md) | | What does it take? | A JSON body of type [`GetPatternInput`](https://docs.plotwork.dev/api/schemas.md#getpatterninput). | | What does it return? | Status 200 with a body of type [`GetPatternResponse`](https://docs.plotwork.dev/api/schemas.md#getpatternresponse). | | What can go wrong? | `400` the body failed schema validation; `401` the api key is missing or not valid; `403` the account is on the free plan, which does not include the rest drawing and checking routes (plan.rest_not_included); `413` the body is larger than the size cap; `429` too many requests. Every failure uses the one error envelope. | ## What it does Takes a pattern id from the find route, a flavour (neutral by default, or aws, azure, gcp), and optional slot values that rename its parts. Answers with the valid Plotwork source, every slot with its meaning and value, the advice on what to keep, rename, and split, and the groups and nodes as a flat tree. An unknown id, flavour, or slot, or a value with a quote in it, is a normal 200 result with found false and every problem with its code and a hint, never an error. Use it when you will edit the source before drawing; use the draw route when only the names change. ## Request body The body has the type [`GetPatternInput`](https://docs.plotwork.dev/api/schemas.md#getpatterninput). | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The pattern id, as find_pattern returns it, such as three-tier-network. at least 1 character; at most 60 characters. | | `provider` | `"neutral" \| "aws" \| "azure" \| "gcp"` | no | neutral (generic glyphs, no vendor names; the default), or aws, azure, or gcp for that provider's icons. Not every pattern comes in every flavour; the answer lists the ones it has. default "neutral". | | `slots` | `object` | no | Slot values keyed by slot id, such as {"office": "Branch office"}: plain text of up to 60 characters, no quotes or line breaks. Omit a slot to keep its default. get_pattern lists the slots and what they mean. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The filled pattern, or the problems that stopped it. | [`GetPatternResponse`](https://docs.plotwork.dev/api/schemas.md#getpatternresponse) | | 400 | The body failed schema validation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | The API key is missing or not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The account is on the Free plan, which does not include the REST drawing and checking routes (plan.rest_not_included). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than the size cap. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`GetPatternResponse`](https://docs.plotwork.dev/api/schemas.md#getpatternresponse). | Field | Type | Required | Description | |---|---|---|---| | `found` | `boolean` | yes | True when the pattern and flavour exist and the slot values are usable. When false, problems says why and nothing else is filled. | | `id` | `string` | yes | The pattern id that was asked for. | | `title` | `string \| null` | yes | | | `patternSummary` | `string \| null` | yes | One sentence: what the pattern shows. | | `whenToUse` | `string \| null` | yes | | | `kind` | `string \| null` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `flavour` | `"neutral" \| "aws" \| "azure" \| "gcp" \| null` | yes | The flavour that was filled. | | `flavours` | `("neutral" \| "aws" \| "azure" \| "gcp")[]` | yes | Every flavour this pattern comes in; empty when the pattern was not found. | | `source` | `string \| null` | yes | Plotwork source, valid and ready to use; null when not found. | | `slots` | `object[]` | yes | Every slot the source uses, with the value it has. | | `slots[].id` | `string` | yes | The slot id; the key to use in slots. | | `slots[].value` | `string` | yes | The text the slot has in the source: the value sent, or the default. | | `slots[].default` | `string` | yes | The default text. | | `slots[].meaning` | `string` | yes | What belongs in this slot. | | `slots[].isDefault` | `boolean` | yes | True when the slot kept its default. | | `notes` | `object \| null` | yes | How to adapt the pattern: what to keep, what to rename, and when to split. | | `notes.keep` | `string` | yes | What to leave exactly as it is. | | `notes.rename` | `string` | yes | What to rename or replace, and how. | | `notes.split` | `string` | yes | When the diagram has grown too big, and where to cut it. | | `problems` | `object[]` | yes | Mistakes in the id, the flavour, or the slot values, each with a fix. Empty when found. | | `problems[].code` | `"unknown-pattern" \| "unknown-flavour" \| "unknown-slot" \| "bad-slot-value" \| "bad-source"` | yes | | | `problems[].message` | `string` | yes | | | `problems[].hint` | `string` | yes | How to fix it. | | `tree` | `object[]` | yes | The groups, overlays, and nodes as drawn, depth first, so you can see what to rename. | | `tree[].depth` | `integer` | yes | How deep the item sits: 0 for the top level. | | `tree[].kind` | `"group" \| "overlay" \| "node" \| "actor" \| "table"` | yes | | | `tree[].id` | `string` | yes | The id written in the source. | | `tree[].label` | `string` | yes | | | `tree[].type` | `string \| null` | yes | The group type, node type, or actor type; null for a table or an untyped group. | | `outline` | `string[]` | yes | The text description of the diagram. | | `next` | `string` | yes | The next step in one sentence. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [`get_pattern`](https://docs.plotwork.dev/mcp/tools/get-pattern.md) | The same operation as an MCP tool | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Draw from pattern > Page: https://docs.plotwork.dev/api/post-v1-patterns-draw. Index of every page: https://docs.plotwork.dev/llms.txt Fill a pattern and draw it. `POST /v1/patterns/draw` ## TL;DR | Question | Answer | |---|---| | What does it do? | Fill a pattern and draw it. | | Which tool does the same? | [`draw_from_pattern`](https://docs.plotwork.dev/mcp/tools/draw-from-pattern.md) | | What does it take? | A JSON body of type [`DrawFromPatternInput`](https://docs.plotwork.dev/api/schemas.md#drawfrompatterninput). | | What does it return? | Status 200 with a body of type [`DrawFromPatternResponse`](https://docs.plotwork.dev/api/schemas.md#drawfrompatternresponse). | | What can go wrong? | `400` the body failed schema validation; `401` the api key is missing or not valid; `403` the account is on the free plan, which does not include the rest drawing and checking routes (plan.rest_not_included); `413` the body is larger than the size cap; `429` too many requests. Every failure uses the one error envelope. | ## What it does Fills a pattern for a flavour with the slot values you send, validates the result, and returns the source, the slots, the advice on adapting it, the quality warnings, a plain-text outline, the natural size, and the SVG for the light theme, the dark theme, or both. An unknown id, flavour, or slot, or a value with a quote in it, is a normal 200 result with found false and every problem with its code and a hint, never an error. Prefer this route over calling the get route and then the draw, validate, outline, and render routes when a pattern fits as it is and only names change. ## Request body The body has the type [`DrawFromPatternInput`](https://docs.plotwork.dev/api/schemas.md#drawfrompatterninput). | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The pattern id, as find_pattern returns it, such as three-tier-network. at least 1 character; at most 60 characters. | | `provider` | `"neutral" \| "aws" \| "azure" \| "gcp"` | no | neutral (generic glyphs, no vendor names; the default), or aws, azure, or gcp for that provider's icons. Not every pattern comes in every flavour; the answer lists the ones it has. default "neutral". | | `slots` | `object` | no | Slot values keyed by slot id, such as {"office": "Branch office"}: plain text of up to 60 characters, no quotes or line breaks. Omit a slot to keep its default. get_pattern lists the slots and what they mean. | | `themes` | `"light" \| "dark" \| "both"` | no | light, dark, or both (the default): the light picture and the dark one a dark page needs. default "both". | | `detail` | `"summary" \| "full"` | no | summary (the default), or full: the whole outline, and the source embedded in each SVG. default "summary". | | `svg` | `"auto" \| "always" \| "never"` | no | auto (the default) returns the SVGs, but over MCP leaves them out when the answer would be too big for a client (picturesOmitted). always returns them at any size. never draws nothing, which is cheaper. The HTTP API treats auto as always. default "auto". | | `embedSource` | `boolean` | no | Write the source into each SVG so read_diagram can give it back. Set it when you save the SVG in a repository or publish it to edit later; anyone with the file or link can then read the source. default false. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The filled pattern with its check and pictures, or the problems that stopped it. | [`DrawFromPatternResponse`](https://docs.plotwork.dev/api/schemas.md#drawfrompatternresponse) | | 400 | The body failed schema validation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | The API key is missing or not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The account is on the Free plan, which does not include the REST drawing and checking routes (plan.rest_not_included). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than the size cap. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`DrawFromPatternResponse`](https://docs.plotwork.dev/api/schemas.md#drawfrompatternresponse). | Field | Type | Required | Description | |---|---|---|---| | `found` | `boolean` | yes | True when the pattern and flavour exist and the slot values are usable. When false, problems says why and nothing else is filled. | | `id` | `string` | yes | The pattern id that was asked for. | | `title` | `string \| null` | yes | | | `patternSummary` | `string \| null` | yes | One sentence: what the pattern shows. | | `whenToUse` | `string \| null` | yes | | | `kind` | `string \| null` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `flavour` | `"neutral" \| "aws" \| "azure" \| "gcp" \| null` | yes | The flavour that was filled. | | `flavours` | `("neutral" \| "aws" \| "azure" \| "gcp")[]` | yes | Every flavour this pattern comes in; empty when the pattern was not found. | | `source` | `string \| null` | yes | Plotwork source, valid and ready to use; null when not found. | | `slots` | `object[]` | yes | Every slot the source uses, with the value it has. | | `slots[].id` | `string` | yes | The slot id; the key to use in slots. | | `slots[].value` | `string` | yes | The text the slot has in the source: the value sent, or the default. | | `slots[].default` | `string` | yes | The default text. | | `slots[].meaning` | `string` | yes | What belongs in this slot. | | `slots[].isDefault` | `boolean` | yes | True when the slot kept its default. | | `notes` | `object \| null` | yes | How to adapt the pattern: what to keep, what to rename, and when to split. | | `notes.keep` | `string` | yes | What to leave exactly as it is. | | `notes.rename` | `string` | yes | What to rename or replace, and how. | | `notes.split` | `string` | yes | When the diagram has grown too big, and where to cut it. | | `problems` | `object[]` | yes | Mistakes in the id, the flavour, or the slot values, each with a fix. Empty when found. | | `problems[].code` | `"unknown-pattern" \| "unknown-flavour" \| "unknown-slot" \| "bad-slot-value" \| "bad-source"` | yes | | | `problems[].message` | `string` | yes | | | `problems[].hint` | `string` | yes | How to fix it. | | `valid` | `boolean` | yes | True when the source has no errors. When false, nothing was drawn: fix the errors and call again. | | `errors` | `Diagnostic[]` | yes | Every error found in one pass, each with line, column, and a hint. Empty when valid. | | `errors[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `errors[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `errors[].message` | `string` | yes | What is wrong, in one sentence. | | `errors[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `errors[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `errors[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `errors[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `warnings` | `Diagnostic[]` | yes | Quality warnings for a valid diagram: too dense, too wide, text cut, unconnected nodes, and suggestions for an icon or a group type. Empty when invalid. | | `warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `summary` | `string \| null` | yes | One-line description of the diagram, usable as alt text; null when invalid. | | `outline` | `string[]` | yes | The diagram as plain text in reading order, to check it says what you meant; empty when invalid. | | `outlineTruncated` | `boolean` | yes | True when the summary level cut the outline short. Call again with detail "full" to read all of it. | | `size` | `object \| null` | yes | Natural size of the laid-out diagram; null when invalid. | | `size.width` | `integer` | yes | Width in px. minimum 1. | | `size.height` | `integer` | yes | Height in px. minimum 1. | | `quality` | `object` | no | How clean the picture looks (score, grade, measures) and up to three changes that would help; read it instead of looking. Absent when invalid. | | `quality.score` | `integer` | yes | 0 to 100; 100 is a picture with nothing to fix. The same diagram always gets the same score. minimum 0; maximum 100. | | `quality.grade` | `"clean" \| "fair" \| "cluttered"` | yes | clean is 85 or more, fair 60 or more, cluttered below. | | `quality.measures` | `object` | yes | What the score is made from: crossings, lineOverBox, labelOverlaps, smallestTextPx (in an 880px column), aspectRatio, emptySpace (0 to 1), longestLine (px), detour (1 is straight), the counts of boxes, lines, labels, series, and slices, and any measure the kind adds. | | `quality.hints` | `string[]` | yes | Up to three changes, the one worth most first, each naming the statement or option to edit. | | `quality.line` | `string` | yes | The grade, the score, and the first hint in one line. | | `notices` | `Note[]` | no | Information notes, such as an icon alias drawn as the real icon; absent when there is none. | | `notices[].code` | `string` | yes | A stable code, such as I122 for an icon alias. | | `notices[].severity` | `"info"` | yes | A note never stops a drawing and is not a warning. | | `notices[].message` | `string` | yes | | | `notices[].line` | `integer` | yes | minimum 1. | | `notices[].column` | `integer` | yes | minimum 1. | | `notices[].length` | `integer` | yes | minimum 1. | | `notices[].hint` | `string \| null` | yes | The text to write instead. | | `renders` | `object[]` | yes | One picture per requested color scheme, light first; empty when invalid, when svg is never, and when every picture was left out (see picturesOmitted). | | `renders[].theme` | `"light" \| "dark"` | yes | The color scheme this picture is drawn in. | | `renders[].svg` | `string` | yes | Self-contained SVG markup with an accessible title and description. | | `renders[].size` | `Size` | yes | Natural size of a rendered diagram in px. | | `renders[].size.width` | `integer` | yes | Width in px. minimum 1. | | `renders[].size.height` | `integer` | yes | Height in px. minimum 1. | | `picturesOmitted` | `object` | no | Present only when pictures were left out because the answer would have been too big for an AI client to read in one piece. | | `picturesOmitted.themes` | `("light" \| "dark")[]` | yes | The color schemes whose pictures were left out. | | `picturesOmitted.characters` | `integer` | yes | How many characters the pictures left out came to. | | `picturesOmitted.limit` | `integer` | yes | The most characters one answer may hold before the pictures are left out. | | `picturesOmitted.why` | `string` | yes | Why they were left out, and what to do. | | `viewUrl` | `string` | no | A link that shows a valid diagram in a browser; give it to the person who wants to look. The source is in the part after #, never sent to a server, but anyone with the link can read it. Absent when too long; next says so. | | `pngUrl` | `string` | no | The viewUrl with &download=png: opening it in a browser saves the diagram as a PNG for Slack, email, or a wiki. The PNG is drawn in the person's browser, not on a server, so it is a link to open, not an image address to embed. Absent when too long. | | `svgUrl` | `string` | no | The viewUrl with &download=svg: a person opens it to download the SVG. Not a file address for curl; use saveCommand. Absent when too long. | | `saveCommand` | `string` | no | Run in a shell to save the SVG into the repository: the CLI decodes the link locally, fetches nothing, and embeds the source. Set --out to the path; add --theme dark for dark. Absent when too long. | | `next` | `string` | yes | The next step in one sentence. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [`draw_from_pattern`](https://docs.plotwork.dev/mcp/tools/draw-from-pattern.md) | The same operation as an MCP tool | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Get diagram syntax > Page: https://docs.plotwork.dev/api/get-v1-vocabulary. Index of every page: https://docs.plotwork.dev/llms.txt Get the diagram language reference. `GET /v1/vocabulary` ## TL;DR | Question | Answer | |---|---| | What does it do? | Get the diagram language reference. | | Which tool does the same? | [`get_diagram_syntax`](https://docs.plotwork.dev/mcp/tools/get-diagram-syntax.md) | | What does it take? | `kind`, `topic` in the query string. | | What does it return? | Status 200 with a body of type [`SyntaxResponse`](https://docs.plotwork.dev/api/schemas.md#syntaxresponse). | | What can go wrong? | `400` the query failed schema validation; `401` the api key is missing or not valid; `403` the account is on the free plan, which does not include the rest drawing and checking routes (plan.rest_not_included); `429` too many requests. Every failure uses the one error envelope. | ## What it does With no kind, the overview: the tables every kind shares (node types with default icons and tones, arrows, line options, icon sets, limits, how to edit a picture) and one line about each kind. With a kind, that kind in full: its statements, limits, and one tested example, and the tables that apply to it. Built from the constants the parser uses, so it cannot drift. ## Query parameters | Field | Type | Required | Description | |---|---|---|---| | `kind` | `"flow" \| "sequence" \| "schema" \| "git" \| "gantt" \| "class" \| "state" \| "timeline" \| "journey" \| "mindmap" \| "treemap" \| "pie" \| "quadrant" \| "layers" \| "c4" \| "matrix" \| "sankey" \| "xychart" \| "tree" \| "swimlane"` | no | The one kind to read in full. Omit it for the overview: the language every kind shares and one line about each kind. | | `topic` | `"import-formats"` | no | import-formats: what import_source reads, draws, and takes, a line each. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The language reference. | [`SyntaxResponse`](https://docs.plotwork.dev/api/schemas.md#syntaxresponse) | | 400 | The query failed schema validation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | The API key is missing or not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The account is on the Free plan, which does not include the REST drawing and checking routes (plan.rest_not_included). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`SyntaxResponse`](https://docs.plotwork.dev/api/schemas.md#syntaxresponse). | Field | Type | Required | Description | |---|---|---|---| | `answer` | `string` | yes | What this answer is: overview, a topic (import-formats), or the kind it describes in full (flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today; later versions add kinds, so expect others). | | `howToGetAKind` | `string` | yes | In plain words: what this answer holds and how to get the rest. | | `kinds` | `string[]` | yes | Header keywords: the first line names the kind. | | `kindDetails` | `object[]` | yes | The overview lists every kind in the order of kinds, with its header, noun, and summary, and a pointer in more. A kind answer holds that kind alone, in full. | | `kindDetails[].kind` | `string` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `kindDetails[].header` | `string` | yes | The header line, such as flow "Title". | | `kindDetails[].noun` | `string` | yes | What the kind's diagrams are called in a sentence, such as flow or database schema. | | `kindDetails[].summary` | `string` | yes | One sentence: what the kind draws. | | `kindDetails[].statements` | `object[]` | no | The statements the kind adds to the header. Empty for a kind whose language the shared tables of this reference already describe. | | `kindDetails[].statements[].syntax` | `string` | yes | The statement with its parts named, such as: task <id> "Name" days=<n>. | | `kindDetails[].statements[].meaning` | `string` | yes | What the statement does, and what it needs. | | `kindDetails[].statements[].example` | `string` | yes | One line that parses. | | `kindDetails[].limits` | `object[]` | no | The quality thresholds the kind adds. Empty for a kind whose limits are in the limits table of this reference. | | `kindDetails[].limits[].name` | `string` | yes | What is counted, such as tasks. | | `kindDetails[].limits[].value` | `integer` | yes | More than this in one diagram draws a quality warning. | | `kindDetails[].limits[].meaning` | `string` | yes | Why the limit exists and what to do past it. | | `kindDetails[].more` | `string` | no | Only in the overview, where statements and limits are left out: what a call with this kind returns that the overview does not, and how to ask for it. | | `nodeTypes` | `object[]` | no | | | `nodeTypes[].type` | `"person" \| "agent" \| "client" \| "service" \| "gateway" \| "function" \| "database" \| "cache" \| "queue" \| "storage" \| "identity" \| "secret" \| "external" \| "step" \| "decision" \| "terminal"` | yes | | | `nodeTypes[].shape` | `"card" \| "pill"` | yes | How the node is drawn. | | `nodeTypes[].isTinted` | `boolean` | yes | True when the card is filled and outlined in its tone, as for decisions. | | `nodeTypes[].defaultTone` | `"neutral" \| "blue" \| "violet" \| "aqua" \| "orange" \| "green" \| "amber" \| "red"` | yes | Color family used when tone= is not set. | | `nodeTypes[].defaultIcon` | `string \| null` | yes | Icon used when icon= is not set, or null for none. | | `nodeTypes[].isDashed` | `boolean` | yes | True when the outline is dashed, as for external systems. | | `nodeTypes[].usableAsActor` | `boolean` | yes | True when a sequence diagram actor may use this type. | | `tones` | `("neutral" \| "blue" \| "violet" \| "aqua" \| "orange" \| "green" \| "amber" \| "red")[]` | no | | | `directions` | `("auto" \| "right" \| "down" \| "left" \| "up")[]` | no | Values for direction= on a flow header. | | `arrows` | `object[]` | no | | | `arrows[].token` | `string` | yes | The arrow as written in source. | | `arrows[].line` | `"solid" \| "dashed" \| "dotted"` | yes | How the line is drawn. | | `arrows[].heads` | `"end" \| "both" \| "none"` | yes | Which ends carry an arrowhead. | | `arrows[].meaning` | `string` | yes | What the arrow says, so the right one is chosen. | | `blockTypes` | `("alt" \| "opt" \| "loop" \| "par")[]` | no | Frames a sequence diagram can wrap around messages. | | `columnFlags` | `("pk" \| "fk" \| "unique" \| "nullable")[]` | no | Flags a schema column row may carry. | | `themes` | `("light" \| "dark")[]` | no | | | `iconSets` | `("lucide" \| "simple" \| "status" \| "aws" \| "gcp" \| "azure" \| "alibaba" \| "oci" \| "ibm" \| "cloudflare" \| "digitalocean" \| "vercel")[]` | no | | | `iconCount` | `integer` | no | Number of bundled icons; use search_icons to find names. | | `iconProviders` | `object[]` | no | Cloud and platform providers, each an icon set holding the provider (set:cloud) and its products. | | `iconProviders[].set` | `"aws" \| "gcp" \| "azure" \| "alibaba" \| "oci" \| "ibm" \| "cloudflare" \| "digitalocean" \| "vercel"` | yes | Icon set prefix, as in aws:lambda. | | `iconProviders[].title` | `string` | yes | The provider the set stands for. | | `iconProviders[].iconCount` | `integer` | yes | Icons in the set: the provider itself (set:cloud) and its products. | | `iconProviders[].example` | `string` | yes | One icon of the set that is bundled, for example aws:lambda. | | `overlay` | `object` | no | A dashed frame around members that may sit in different groups; it owns none of them. | | `overlay.options` | `string[]` | yes | Options an overlay statement takes after its name, as in: overlay id "Name" members=a,b type=scale-group. | | `overlay.frame` | `"solid" \| "dashed" \| "dotted"` | yes | How an overlay is always drawn. | | `overlay.meaning` | `string` | yes | What an overlay is for, so it is used where a group would be wrong. | | `group` | `object` | no | How a flow group looks and what it accepts; its corner tab shows its icon and name. | | `group.options` | `string[]` | yes | Options a group statement takes after its name, as in: group id "Name" type=region provider=aws {. | | `group.defaultTone` | `"neutral" \| "blue" \| "violet" \| "aqua" \| "orange" \| "green" \| "amber" \| "red"` | yes | Color family of a group with no type when tone= is not set. | | `group.example` | `object` | yes | A complete architecture diagram: typed groups nested by provider, a provider icon on every node, stack marks, and a dotted edge. Copy and edit it. | | `group.example.kind` | `string` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `group.example.title` | `string` | yes | | | `group.example.source` | `string` | yes | A complete diagram that is checked by the build; copy and edit it. | | `group.types` | `object[]` | yes | Every group type. A type gives the group a default icon, tone, frame style, and a shade that deepens with nesting; icon= and tone= override them. | | `group.types[].type` | `"cloud" \| "account" \| "region" \| "zone" \| "network" \| "subnet" \| "public-subnet" \| "private-subnet" \| "management-subnet" \| "isolated-subnet" \| "resource-group" \| "cluster" \| "scale-group" \| "service-plan" \| "web-tier" \| "app-tier" \| "data-tier" \| "storage-account" \| "messaging" \| "edge" \| "security" \| "identity-boundary" \| "trust-boundary" \| "observability" \| "ci-cd" \| "environment" \| "cloud-boundary" \| "on-premises" \| "client-side" \| "external-systems"` | yes | Value for type= on a group. | | `group.types[].title` | `string` | yes | What the frame is called in the text description. | | `group.types[].meaning` | `string` | yes | What the author means by this type, so the right one is chosen. | | `group.types[].tone` | `"neutral" \| "blue" \| "violet" \| "aqua" \| "orange" \| "green" \| "amber" \| "red"` | yes | Color family the frame and tab get when tone= is not set. | | `group.types[].frame` | `"solid" \| "dashed" \| "dotted"` | yes | How the outline is drawn. | | `group.types[].icon` | `string` | yes | Icon in the corner tab when the group has no provider. | | `group.types[].providerIcons` | `object` | yes | Icon in the corner tab for each provider whose icon differs from the generic one, keyed by provider set. | | `lineOptions` | `object[]` | no | Options that animate a line (animate=on makes dashes travel toward the arrowhead) or number the steps of a flow or sequence (numbered=on, start=, and step= on a line). All are off by default. | | `lineOptions[].key` | `string` | yes | The option as written, such as animate or step. | | `lineOptions[].scope` | `"header" \| "line"` | yes | header: on the flow or sequence header line. line: on an edge or a message. | | `lineOptions[].values` | `string` | yes | What the option takes. | | `lineOptions[].meaning` | `string` | yes | What it does, and the cases where it is refused. | | `lineOptions[].example` | `string` | yes | One line that parses. | | `labelIcons` | `object[]` | no | Edge labels that get a tick or cross automatically; icon=none on the edge removes it. | | `labelIcons[].label` | `string` | yes | An edge label that gets an icon automatically, matched whole and ignoring case. | | `labelIcons[].icon` | `string` | yes | The icon the label gets. | | `verbIcons` | `object[]` | no | The icons a flow edge label draws when it names a relationship with a verb. icon= on the edge wins, icon=none removes it, and icons=off on the header turns them all off. | | `verbIcons[].icon` | `string` | yes | The icon drawn in the label. | | `verbIcons[].words` | `string[]` | yes | Phrases that draw it: the whole label, or its first words followed by what is acted on ("reads orders"). | | `style` | `string[]` | no | How to make a flow diagram look finished: verbs on arrows, role= on lines that mean something, typed groups, product names. | | `autoIcons` | `object` | no | Items with no icon= get the icon their label names for certain (a brand, a cloud product, a common role). icon=none keeps one item plain; icons=off on the header keeps them all plain. | | `autoIcons.key` | `string` | yes | The header option: icons. | | `autoIcons.values` | `string` | yes | What it takes. | | `autoIcons.meaning` | `string` | yes | What it does, and how one item opts out. | | `autoIcons.example` | `string` | yes | One header line that parses. | | `roleIcons` | `object[]` | no | The plain icons for common roles, drawn on flow and sequence service and external items, C4 elements, layers blocks, mind map topics, and swimlane lanes. The last role word of the label wins. | | `roleIcons[].icon` | `string` | yes | The icon drawn. | | `roleIcons[].words` | `string[]` | yes | Whole words of a label of three words or fewer that draw it. | | `statusIcons` | `object[]` | no | Colored status and outcome icons: a green check, a red X, and so on. | | `statusIcons[].ref` | `string` | yes | Value for icon=, such as status:success. | | `statusIcons[].tone` | `"neutral" \| "blue" \| "violet" \| "aqua" \| "orange" \| "green" \| "amber" \| "red"` | yes | Color the icon is always drawn in, whatever tone its owner has. | | `statusIcons[].meaning` | `string` | yes | What the icon says, so the right one is chosen. | | `limits` | `object` | no | Bounds and quality thresholds. | | `limits.maxSourceLength` | `integer` | yes | | | `limits.maxFlowNodes` | `integer` | yes | Quality lint: more nodes than this warns, for a diagram with no groups. | | `limits.nodesPerGroup` | `integer` | yes | Each group of a flow diagram raises the node limit by this many. | | `limits.maxFlowNodesCeiling` | `integer` | yes | The node limit never rises above this, whatever the groups. | | `limits.maxSchemaTables` | `integer` | yes | | | `limits.maxSequenceActors` | `integer` | yes | | | `limits.maxNumberedSteps` | `integer` | yes | More numbered steps than this in one diagram warns (W200). | | `limits.maxStackMarks` | `integer` | yes | Most technology marks a node carries in stack=. | | `limits.targetWidth` | `integer` | yes | Column width in px that diagrams are checked against. | | `editing` | `object` | no | How to edit an existing Plotwork SVG: read it back with read_diagram, change the source, draw it, and save or publish it again. | | `editing.steps` | `string[]` | yes | In order: how to change a diagram that already exists as a Plotwork SVG, a hosted link or a file in the repository. | | `editing.embedSource` | `string` | yes | When to set embedSource true on draw_diagram, render_diagram, or publish_diagram, and what it means for who can read the source. | | `diagnosticCodes` | `object[]` | no | | | `diagnosticCodes[].name` | `string` | yes | Identifier of the code. | | `diagnosticCodes[].code` | `string` | yes | The code to branch on, such as E120. | | `diagnosticCodes[].severity` | `"error" \| "warning"` | yes | | | `examples` | `object[]` | yes | Tested examples: the overview holds the ones for the kinds the shared tables describe in full, a kind answer holds its own. | | `examples[].kind` | `string` | yes | A diagram kind: flow, sequence, schema, git, gantt, class, state, timeline, journey, mindmap, treemap, pie, quadrant, layers, c4, matrix, sankey, xychart, tree, or swimlane today. Later versions add kinds, so expect others. | | `examples[].title` | `string` | yes | | | `examples[].source` | `string` | yes | A complete diagram that is checked by the build; copy and edit it. | | `importFormats` | `object[]` | no | Only for topic import-formats: every format import_source reads, a line each. | | `importFormats[].format` | `string` | yes | The format, as import_source takes it. | | `importFormats[].reads` | `string` | yes | What it reads. | | `importFormats[].draws` | `string` | yes | The diagram it draws. | | `importFormats[].options` | `string` | yes | The options it takes. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [`get_diagram_syntax`](https://docs.plotwork.dev/mcp/tools/get-diagram-syntax.md) | The same operation as an MCP tool | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Email a sign-in code > Page: https://docs.plotwork.dev/api/post-auth-code. Index of every page: https://docs.plotwork.dev/llms.txt Sends a six-digit code to the address. `POST /auth/code` ## TL;DR | Question | Answer | |---|---| | What does it do? | Email a sign-in code. | | What does it take? | A JSON body of type [`RequestCodeBody`](https://docs.plotwork.dev/api/schemas.md#requestcodebody). | | What does it return? | Status 202 with a body of type [`Accepted`](https://docs.plotwork.dev/api/schemas.md#accepted). | | What can go wrong? | `400` the address is not valid; `403` the request came from an origin that may not sign in; `429` too many requests; `502` the email could not be sent. Every failure uses the one error envelope. | ## What it does Sends a six-digit code to the address. There is no separate sign-up: the first successful sign-in makes the account. The answer is the same whether or not an email went out, so it reveals nothing about the address. ## Request body The body has the type [`RequestCodeBody`](https://docs.plotwork.dev/api/schemas.md#requestcodebody). | Field | Type | Required | Description | |---|---|---|---| | `email` | `string` | yes | at least 3 characters; at most 254 characters. | ## Responses | Status | Meaning | Body | |---|---|---| | 202 | Accepted. | [`Accepted`](https://docs.plotwork.dev/api/schemas.md#accepted) | | 400 | The address is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The request came from an origin that may not sign in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 502 | The email could not be sent. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 202 answer has the type [`Accepted`](https://docs.plotwork.dev/api/schemas.md#accepted). | Field | Type | Required | Description | |---|---|---|---| | `accepted` | `true` | yes | | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Sign in with a code > Page: https://docs.plotwork.dev/api/post-auth-verify. Index of every page: https://docs.plotwork.dev/llms.txt Checks the code from the email. `POST /auth/verify` ## TL;DR | Question | Answer | |---|---| | What does it do? | Sign in with a code. | | What does it take? | A JSON body of type [`VerifyCodeBody`](https://docs.plotwork.dev/api/schemas.md#verifycodebody). | | What does it return? | Status 200 with a body of type [`SignInResponse`](https://docs.plotwork.dev/api/schemas.md#signinresponse). | | What can go wrong? | `400` the body is not valid; `401` the code is wrong, expired, or used; `403` the request came from an origin that may not sign in, or the account is suspended; `429` too many requests. Every failure uses the one error envelope. | ## What it does Checks the code from the email. On success it makes the account if there is none, opens a session, and sets the session cookie, which is HTTP-only and shared by every Plotwork subdomain, and a plain marker cookie, `plotwork_signed_in`, that tells the site and the docs a session exists. An account the operator suspended gets no session. A code dies after five wrong guesses or ten minutes. ## Request body The body has the type [`VerifyCodeBody`](https://docs.plotwork.dev/api/schemas.md#verifycodebody). | Field | Type | Required | Description | |---|---|---|---| | `email` | `string` | yes | at least 3 characters; at most 254 characters. | | `code` | `string` | yes | pattern `^\d{6}$`. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | Signed in. | [`SignInResponse`](https://docs.plotwork.dev/api/schemas.md#signinresponse) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | The code is wrong, expired, or used. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The request came from an origin that may not sign in, or the account is suspended. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`SignInResponse`](https://docs.plotwork.dev/api/schemas.md#signinresponse). | Field | Type | Required | Description | |---|---|---|---| | `user` | `Account` | yes | | | `user.id` | `string` | yes | | | `user.email` | `string` | yes | | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Sign out > Page: https://docs.plotwork.dev/api/post-auth-logout. Index of every page: https://docs.plotwork.dev/llms.txt Ends the session and clears the cookies. `POST /auth/logout` ## TL;DR | Question | Answer | |---|---| | What does it do? | Sign out. | | What does it take? | Nothing. | | What does it return? | Status 204 with no body. | | What can go wrong? | `403` the request came from an origin that may not sign out; `429` too many requests. Every failure uses the one error envelope. | ## What it does Ends the session and clears the cookies. Safe to call when already signed out. ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Signed out. | none | | 403 | The request came from an origin that may not sign out. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Ask whether an address signs in with single sign-on > Page: https://docs.plotwork.dev/api/post-auth-sso-discover. Index of every page: https://docs.plotwork.dev/llms.txt `{ sso: true }` only when the address's domain is verified by an organisation whose single sign-on is on and whose plan has it; otherwise `{ sso: false }` and the emailed code works as before. `POST /auth/sso/discover` ## TL;DR | Question | Answer | |---|---| | What does it do? | Ask whether an address signs in with single sign-on. | | What does it take? | A JSON body of type [`SsoDiscoverBody`](https://docs.plotwork.dev/api/schemas.md#ssodiscoverbody). | | What does it return? | Status 200 with a body of type [`SsoDiscover`](https://docs.plotwork.dev/api/schemas.md#ssodiscover). | | What can go wrong? | `400` the address is not valid; `403` the request came from an origin that may not sign in; `429` too many requests. Every failure uses the one error envelope. | ## What it does `{ sso: true }` only when the address's domain is verified by an organisation whose single sign-on is on and whose plan has it; otherwise `{ sso: false }` and the emailed code works as before. Names no organisation. ## Request body The body has the type [`SsoDiscoverBody`](https://docs.plotwork.dev/api/schemas.md#ssodiscoverbody). | Field | Type | Required | Description | |---|---|---|---| | `email` | `string` | yes | at least 3 characters; at most 254 characters. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | Whether to offer single sign-on. | [`SsoDiscover`](https://docs.plotwork.dev/api/schemas.md#ssodiscover) | | 400 | The address is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The request came from an origin that may not sign in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`SsoDiscover`](https://docs.plotwork.dev/api/schemas.md#ssodiscover). | Field | Type | Required | Description | |---|---|---|---| | `sso` | `boolean` | yes | True when the address's domain signs in through an organisation's identity provider. Names no organisation. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Start signing in through an organisation's identity provider > Page: https://docs.plotwork.dev/api/post-auth-sso-start. Index of every page: https://docs.plotwork.dev/llms.txt Answers the provider's address and sets the flow cookie that binds the sign-in to this browser for ten minutes. `POST /auth/sso/start` ## TL;DR | Question | Answer | |---|---| | What does it do? | Start signing in through an organisation's identity provider. | | What does it take? | A JSON body of type [`SsoStartBody`](https://docs.plotwork.dev/api/schemas.md#ssostartbody). | | What does it return? | Status 200 with a body of type [`SsoStart`](https://docs.plotwork.dev/api/schemas.md#ssostart). | | What can go wrong? | `400` the address is not valid; `403` the request came from an origin that may not sign in, or sent none; `404` the address does not sign in with single sign-on (sso.not_configured); `429` too many requests; `503` the portal's address is not set on this server. Every failure uses the one error envelope. | ## What it does Answers the provider's address and sets the flow cookie that binds the sign-in to this browser for ten minutes. OpenID Connect: authorization code with PKCE (S256), state and nonce. SAML: an AuthnRequest on the redirect binding with a RelayState. `returnTo` is kept only when its origin is Plotwork's. 404 sso.not_configured otherwise. ## Request body The body has the type [`SsoStartBody`](https://docs.plotwork.dev/api/schemas.md#ssostartbody). | Field | Type | Required | Description | |---|---|---|---| | `email` | `string` | yes | at least 3 characters; at most 254 characters. | | `returnTo` | `string` | no | Where to send the browser after signing in. Kept only when its origin is one of Plotwork's; otherwise the portal. at most 2000 characters. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The provider's address. | [`SsoStart`](https://docs.plotwork.dev/api/schemas.md#ssostart) | | 400 | The address is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The request came from an origin that may not sign in, or sent none. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | The address does not sign in with single sign-on (sso.not_configured). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | The portal's address is not set on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`SsoStart`](https://docs.plotwork.dev/api/schemas.md#ssostart). | Field | Type | Required | Description | |---|---|---|---| | `url` | `string` | yes | The identity provider's authorization address to send the browser to. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Finish signing in through an identity provider > Page: https://docs.plotwork.dev/api/get-auth-sso-callback. Index of every page: https://docs.plotwork.dev/llms.txt Where the provider sends the browser back. `GET /auth/sso/callback` ## TL;DR | Question | Answer | |---|---| | What does it do? | Finish signing in through an identity provider. | | What does it take? | `code`, `state`, `iss`, `error`, `error_description` in the query string. | | What does it return? | Status 200 with no body. | | What can go wrong? | `302` to the return address, or to the portal's sign-in page with `sso_error`; `429` too many requests; `503` the portal's address is not set on this server. Every failure uses the one error envelope. | ## What it does Where the provider sends the browser back. The flow is spent once and needs the browser's flow cookie; the code is traded with the PKCE verifier; the ID token's signature, issuer, audience, times and nonce are checked; the address must be on a verified domain. Opens a new session and redirects, or redirects to the portal's sign-in page with `sso_error`. ## Query parameters | Field | Type | Required | Description | |---|---|---|---| | `code` | `string` | no | | | `state` | `string` | no | | | `iss` | `string` | no | | | `error` | `string` | no | | | `error_description` | `string` | no | | ## Responses | Status | Meaning | Body | |---|---|---| | 302 | To the return address, or to the portal's sign-in page with `sso_error`. | none | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | The portal's address is not set on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Read an organisation's SAML service provider metadata > Page: https://docs.plotwork.dev/api/get-auth-sso-saml-org-metadata. Index of every page: https://docs.plotwork.dev/llms.txt Our entity id, our assertion consumer service on the POST binding, the email name identifier and signed assertions wanted, for one organisation id. `GET /auth/sso/saml/{org}/metadata` ## TL;DR | Question | Answer | |---|---| | What does it do? | Read an organisation's SAML service provider metadata. | | What does it take? | `org` in the path. | | What does it return? | Status 200 with no body. | | What can go wrong? | `400` not an organisation id; `429` too many requests. Every failure uses the one error envelope. | ## What it does Our entity id, our assertion consumer service on the POST binding, the email name identifier and signed assertions wanted, for one organisation id. The same answer for every id, so it reveals nothing. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The metadata XML. | none | | 400 | Not an organisation id. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Finish signing in through a SAML identity provider > Page: https://docs.plotwork.dev/api/post-auth-sso-saml-org-acs. Index of every page: https://docs.plotwork.dev/llms.txt The assertion consumer service. `POST /auth/sso/saml/{org}/acs` ## TL;DR | Question | Answer | |---|---| | What does it do? | Finish signing in through a SAML identity provider. | | What does it take? | `org` in the path. | | What does it return? | Status 200 with no body. | | What can go wrong? | `303` to the return address, or to the portal's sign-in page with `sso_error`; `400` not an organisation id; `413` the body is too large; `429` too many requests; `503` the portal's address is not set on this server. Every failure uses the one error envelope. | ## What it does The assertion consumer service. The RelayState's flow is spent once and needs the browser's flow cookie; the SAML Worker checks the signature, the issuer, the audience, the destination, the recipient, the times and InResponseTo. Opens a new session and redirects, or redirects to the portal's sign-in page with `sso_error`. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 303 | To the return address, or to the portal's sign-in page with `sso_error`. | none | | 400 | Not an organisation id. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is too large. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | The portal's address is not set on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Read the signed-in account > Page: https://docs.plotwork.dev/api/get-user-me. Index of every page: https://docs.plotwork.dev/llms.txt Returns the person behind the session cookie. `GET /user/me` ## TL;DR | Question | Answer | |---|---| | What does it do? | Read the signed-in account. | | What does it take? | Nothing. | | What does it return? | Status 200 with a body of type [`Account`](https://docs.plotwork.dev/api/schemas.md#account). | | What can go wrong? | `401` not signed in; `429` too many requests. Every failure uses the one error envelope. | ## What it does Returns the person behind the session cookie. The web apps call it to learn whether a visitor is signed in. ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The signed-in account. | [`Account`](https://docs.plotwork.dev/api/schemas.md#account) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`Account`](https://docs.plotwork.dev/api/schemas.md#account). | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `email` | `string` | yes | | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # List your API keys > Page: https://docs.plotwork.dev/api/get-user-api-keys. Index of every page: https://docs.plotwork.dev/llms.txt Returns the active keys, newest first. `GET /user/api-keys` ## TL;DR | Question | Answer | |---|---| | What does it do? | List your API keys. | | What does it take? | Nothing. | | What does it return? | Status 200 with a body of type [`ApiKeyList`](https://docs.plotwork.dev/api/schemas.md#apikeylist). | | What can go wrong? | `401` not signed in; `429` too many requests. Every failure uses the one error envelope. | ## What it does Returns the active keys, newest first. A key is shown as its first characters and its last four. `revealable` says whether the full value can be asked for with the reveal route. ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The active keys. | [`ApiKeyList`](https://docs.plotwork.dev/api/schemas.md#apikeylist) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`ApiKeyList`](https://docs.plotwork.dev/api/schemas.md#apikeylist). | Field | Type | Required | Description | |---|---|---|---| | `items` | `ApiKeySummary[]` | yes | | | `items[].id` | `string` | yes | | | `items[].name` | `string` | yes | | | `items[].prefix` | `string` | yes | | | `items[].last4` | `string` | yes | | | `items[].createdAt` | `integer` | yes | Milliseconds since the epoch. | | `items[].lastUsedAt` | `integer \| null` | yes | Milliseconds since the epoch, or null if never used. | | `items[].revealable` | `boolean` | yes | True when the full key can be shown again with the reveal route. False for a key made before that was possible, or on a server that cannot seal keys. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Create an API key > Page: https://docs.plotwork.dev/api/post-user-api-keys. Index of every page: https://docs.plotwork.dev/llms.txt Makes a key and returns its full value. `POST /user/api-keys` ## TL;DR | Question | Answer | |---|---| | What does it do? | Create an API key. | | What does it take? | A JSON body of type [`CreateApiKeyBody`](https://docs.plotwork.dev/api/schemas.md#createapikeybody). | | What does it return? | Status 201 with a body of type [`CreatedApiKey`](https://docs.plotwork.dev/api/schemas.md#createdapikey). | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` the request came from an origin that may not make changes; `409` the account already holds the most keys allowed; `429` too many requests. Every failure uses the one error envelope. | ## What it does Makes a key and returns its full value. A sealed copy is kept when the server can, so the key can be revealed again; otherwise this answer is the only time it is shown. ## Request body The body has the type [`CreateApiKeyBody`](https://docs.plotwork.dev/api/schemas.md#createapikeybody). | Field | Type | Required | Description | |---|---|---|---| | `name` | `string` | yes | at least 1 character; at most 60 characters; pattern `\S`. | ## Responses | Status | Meaning | Body | |---|---|---| | 201 | The new key, including its full value. | [`CreatedApiKey`](https://docs.plotwork.dev/api/schemas.md#createdapikey) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | The account already holds the most keys allowed. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 201 answer has the type [`CreatedApiKey`](https://docs.plotwork.dev/api/schemas.md#createdapikey). | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `name` | `string` | yes | | | `prefix` | `string` | yes | | | `last4` | `string` | yes | | | `createdAt` | `integer` | yes | Milliseconds since the epoch. | | `lastUsedAt` | `integer \| null` | yes | Milliseconds since the epoch, or null if never used. | | `revealable` | `boolean` | yes | True when the full key can be shown again with the reveal route. False for a key made before that was possible, or on a server that cannot seal keys. | | `key` | `string` | yes | The full key. When `revealable` is false this is the only time it is shown, so store it now. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Reveal an API key > Page: https://docs.plotwork.dev/api/post-user-api-keys-id-reveal. Index of every page: https://docs.plotwork.dev/llms.txt Shows the full value of one of your keys again, when a sealed copy was kept. `POST /user/api-keys/{id}/reveal` ## TL;DR | Question | Answer | |---|---| | What does it do? | Reveal an API key. | | What does it take? | `id` in the path. | | What does it return? | Status 200 with a body of type [`RevealedApiKey`](https://docs.plotwork.dev/api/schemas.md#revealedapikey). | | What can go wrong? | `401` not signed in; `403` the request came from an origin that may not make changes; `404` no such key; `409` this key has no sealed copy to show; `429` too many requests; `503` revealing keys is not set up on this server. Every failure uses the one error envelope. | ## What it does Shows the full value of one of your keys again, when a sealed copy was kept. Audited by key id, never by value. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The key, with its full value. | [`RevealedApiKey`](https://docs.plotwork.dev/api/schemas.md#revealedapikey) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such key. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | This key has no sealed copy to show. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Revealing keys is not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`RevealedApiKey`](https://docs.plotwork.dev/api/schemas.md#revealedapikey). | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `key` | `string` | yes | The full key. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Revoke an API key > Page: https://docs.plotwork.dev/api/delete-user-api-keys-id. Index of every page: https://docs.plotwork.dev/llms.txt Stops the key working at once. `DELETE /user/api-keys/{id}` ## TL;DR | Question | Answer | |---|---| | What does it do? | Revoke an API key. | | What does it take? | `id` in the path. | | What does it return? | Status 204 with no body. | | What can go wrong? | `401` not signed in; `403` the request came from an origin that may not make changes; `404` no such key; `429` too many requests. Every failure uses the one error envelope. | ## What it does Stops the key working at once. A key that is not yours, or is already revoked, is a 404. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Revoked. | none | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such key. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Read the account's usage > Page: https://docs.plotwork.dev/api/get-user-usage. Index of every page: https://docs.plotwork.dev/llms.txt How many metered calls the account made today, the daily limit, when the count resets at 00:00 UTC, and the last 30 days. `GET /user/usage` ## TL;DR | Question | Answer | |---|---| | What does it do? | Read the account's usage. | | What does it take? | Nothing. | | What does it return? | Status 200 with a body of type [`Usage`](https://docs.plotwork.dev/api/schemas.md#usage). | | What can go wrong? | `401` not signed in; `429` too many requests. Every failure uses the one error envelope. | ## What it does How many metered calls the account made today, the daily limit, when the count resets at 00:00 UTC, and the last 30 days. REST API calls and MCP tool calls count; handshakes, tool listings and signing in do not. Reading it is not counted. ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The account's usage. | [`Usage`](https://docs.plotwork.dev/api/schemas.md#usage) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`Usage`](https://docs.plotwork.dev/api/schemas.md#usage). | Field | Type | Required | Description | |---|---|---|---| | `plan` | `"free" \| "pro"` | yes | The plan the account is on, which decides the limit. | | `limit` | `integer` | yes | Metered calls the account may make per UTC day, across every key and connected agent. | | `used` | `integer` | yes | Metered calls made today. minimum 0. | | `remaining` | `integer` | yes | Calls left today. Zero when the limit is used up. minimum 0. | | `resetsAt` | `integer` | yes | When the count goes back to zero (00:00 UTC), in milliseconds since the epoch. | | `today` | `object` | yes | Today's calls, split by how they reached the service. | | `today.api` | `integer` | yes | minimum 0. | | `today.mcp` | `integer` | yes | minimum 0. | | `days` | `UsageDay[]` | yes | The last 30 days, oldest first, ending today. A day with no calls is present with zeros. | | `days[].day` | `string` | yes | The UTC date, as YYYY-MM-DD. | | `days[].api` | `integer` | yes | REST API calls that day. minimum 0. | | `days[].mcp` | `integer` | yes | MCP tool calls that day. minimum 0. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Approve a request to connect an agent > Page: https://docs.plotwork.dev/api/post-user-mcp-approve. Index of every page: https://docs.plotwork.dev/llms.txt Verifies the signed authorization request the MCP server made, makes a hidden API key for the agent, and answers with a signed approval that carries the key sealed. `POST /user/mcp/approve` ## TL;DR | Question | Answer | |---|---| | What does it do? | Approve a request to connect an agent. | | What does it take? | A JSON body of type [`ApproveConnectionBody`](https://docs.plotwork.dev/api/schemas.md#approveconnectionbody). | | What does it return? | Status 200 with a body of type [`Approval`](https://docs.plotwork.dev/api/schemas.md#approval). | | What can go wrong? | `400` the request is malformed, has expired, or was not signed by the mcp server; `401` not signed in; `403` the request came from an origin that may not make changes; `429` too many requests; `503` connecting an agent is not set up on this server. Every failure uses the one error envelope. | ## What it does Verifies the signed authorization request the MCP server made, makes a hidden API key for the agent, and answers with a signed approval that carries the key sealed. The browser then goes to redirectTo. Connecting the same agent again replaces its key. ## Request body The body has the type [`ApproveConnectionBody`](https://docs.plotwork.dev/api/schemas.md#approveconnectionbody). | Field | Type | Required | Description | |---|---|---|---| | `request` | `string` | yes | The signed authorization request, exactly as the MCP server put it in the address. at least 1 character; at most 6144 characters. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | Approved. | [`Approval`](https://docs.plotwork.dev/api/schemas.md#approval) | | 400 | The request is malformed, has expired, or was not signed by the MCP server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Connecting an agent is not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`Approval`](https://docs.plotwork.dev/api/schemas.md#approval). | Field | Type | Required | Description | |---|---|---|---| | `approval` | `string` | yes | The signed approval. It carries the connection key sealed, so it is safe in an address. | | `expiresIn` | `integer` | yes | Seconds the approval stays usable. | | `redirectTo` | `string` | yes | Where to send the browser: the MCP server callback with the request and the approval already attached. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # List the agents you have connected > Page: https://docs.plotwork.dev/api/get-user-mcp-connections. Index of every page: https://docs.plotwork.dev/llms.txt Returns the agents that signed in with a browser and are still connected, newest first. `GET /user/mcp/connections` ## TL;DR | Question | Answer | |---|---| | What does it do? | List the agents you have connected. | | What does it take? | Nothing. | | What does it return? | Status 200 with a body of type [`ConnectionList`](https://docs.plotwork.dev/api/schemas.md#connectionlist). | | What can go wrong? | `401` not signed in; `429` too many requests. Every failure uses the one error envelope. | ## What it does Returns the agents that signed in with a browser and are still connected, newest first. The key behind a connection is never shown. ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The connected agents. | [`ConnectionList`](https://docs.plotwork.dev/api/schemas.md#connectionlist) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`ConnectionList`](https://docs.plotwork.dev/api/schemas.md#connectionlist). | Field | Type | Required | Description | |---|---|---|---| | `items` | `Connection[]` | yes | | | `items[].id` | `string` | yes | | | `items[].clientId` | `string` | yes | The OAuth client id the agent registered with. | | `items[].clientName` | `string` | yes | The name the agent gave itself when it registered. It is a claim, not a fact. | | `items[].connectedAt` | `integer` | yes | Milliseconds since the epoch. | | `items[].lastUsedAt` | `integer \| null` | yes | Milliseconds since the epoch, or null if the agent has not made a call. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Disconnect an agent > Page: https://docs.plotwork.dev/api/delete-user-mcp-connections-key-id. Index of every page: https://docs.plotwork.dev/llms.txt Ends the connection at once: its key stops working, so the next call from the agent is refused. `DELETE /user/mcp/connections/{keyId}` ## TL;DR | Question | Answer | |---|---| | What does it do? | Disconnect an agent. | | What does it take? | `keyId` in the path. | | What does it return? | Status 204 with no body. | | What can go wrong? | `401` not signed in; `403` the request came from an origin that may not make changes; `404` no such connection; `429` too many requests. Every failure uses the one error envelope. | ## What it does Ends the connection at once: its key stops working, so the next call from the agent is refused. A connection that is not yours, or is already ended, is a 404. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `keyId` | `string` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Disconnected. | none | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such connection. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Publish a diagram and get a link > Page: https://docs.plotwork.dev/api/post-v1-diagrams-publish. Index of every page: https://docs.plotwork.dev/llms.txt Renders the source and stores the SVG at a public link that cannot be guessed. `POST /v1/diagrams/publish` ## TL;DR | Question | Answer | |---|---| | What does it do? | Publish a diagram and get a link. | | What does it take? | A JSON body of type [`PublishDiagramBody`](https://docs.plotwork.dev/api/schemas.md#publishdiagrambody). | | What does it return? | Status 200 with a body of type [`PublishAnswer`](https://docs.plotwork.dev/api/schemas.md#publishanswer). | | What can go wrong? | `400` the body failed schema validation, personal and organization were both sent, or an alias was sent for a diagram that lands in an organisation; `401` the api key is missing or not valid; `403` the plan has no room for another diagram (details says which limit, the plan, the limit, and what is used), the key is an organisation key (orgs.key_wrong_org, with details.organization, the id of its organisation), which publishes with post /orgs/{org}/diagrams, an organisation requires its members to publish to its library (orgs.publish_required, with details.organizations), or the role may not publish there (orgs.forbidden, orgs.folder_restricted); `404` the organisation named does not exist, or the account is not a member of it; `409` the diagram is already hosted and locked, and the publish names it or files it elsewhere (hosting.diagram_locked; unlock it in the portal first), or several organisations publish their members' diagrams to their library and none is the default (orgs.publish_ambiguous, with details.organizations); `413` the body is larger than the size cap, or the diagram renders larger than the plan hosts; `422` the diagram source has errors; the diagnostics are in details; `429` too many requests; `502` the diagram could not be stored; `503` hosting is not set up on this server. Every failure uses the one error envelope. | ## What it does Renders the source and stores the SVG at a public link that cannot be guessed. Only the renderer's own output is stored; there is no field for SVG. The same diagram published again from the same account keeps its link (200, created false) and moves its expiry out. 403 hosting.quota_exceeded says which limit was reached; 413 hosting.too_large says the diagram is larger than the plan hosts. ## Request body The body has the type [`PublishDiagramBody`](https://docs.plotwork.dev/api/schemas.md#publishdiagrambody). | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | Diagram source; the first line is the header, such as flow "Title" (draw_diagram lists every kind). at least 1 character; at most 50000 characters. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | | `theme` | `"light" \| "dark" \| "auto"` | no | The color scheme to draw it in: light, dark, or auto (one picture that follows the viewer's light or dark setting). default "light". | | `embedSource` | `boolean` | no | Write the diagram source into the stored SVG, in its metadata, so the picture can be edited later: read_diagram gives the source back from the link. The source then becomes part of the picture, which anyone who has the link can read (for a diagram in an organisation, every member who can open it). The same diagram published with and without it is two pictures with two links. Left out, a personal diagram does not store the source, and a diagram that lands in an organisation follows its keepEditable setting. | | `name` | `string` | no | A label for the diagram in your list and in the Markdown snippet. Optional. at least 1 character; at most 80 characters; pattern `^[^\p{Cc}]+$/u`. | | `folder` | `string \| null` | no | The folder to file the diagram in. Left out, a new diagram goes to the top level and a diagram that is already hosted stays where it is. A path files a new diagram there, or moves the one that is already hosted and keeps its link. null puts it at the top level. | | `alias` | `string` | no | Point a stable link with this name at the diagram, so one address always shows the latest version. The first publish with a name makes the stable link (the answer's stableLink.url) and every later publish with the same name moves it to the new diagram; the address never changes. How many stable links an account keeps depends on its plan. An alias asks for a personal link, so an organisation that requires its library refuses it (orgs.publish_required). Optional. at least 1 character; at most 60 characters; pattern `^[a-z0-9]+(?:-[a-z0-9]+)*$`. | | `organization` | `string` | no | An organisation's slug or id: the diagram goes to that organisation's private library, and the answer has private true and a viewUrl in place of a public link. The account must be a member who may publish there. Left out, the diagram goes to your own library, unless an organisation you belong to publishes its members' diagrams to its library. at least 1 character; at most 64 characters. | | `personal` | `boolean` | no | True asks for a public link in your own library even though an organisation you belong to publishes its members' diagrams to its library by default. An organisation that requires its library refuses it (403 orgs.publish_required). Not with organization. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The diagram was already hosted; its link is unchanged. | [`PublishAnswer`](https://docs.plotwork.dev/api/schemas.md#publishanswer) | | 201 | The diagram was stored. | [`PublishAnswer`](https://docs.plotwork.dev/api/schemas.md#publishanswer) | | 400 | The body failed schema validation, personal and organization were both sent, or an alias was sent for a diagram that lands in an organisation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | The API key is missing or not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The plan has no room for another diagram (details says which limit, the plan, the limit, and what is used), the key is an organisation key (orgs.key_wrong_org, with details.organization, the id of its organisation), which publishes with POST /orgs/{org}/diagrams, an organisation requires its members to publish to its library (orgs.publish_required, with details.organizations), or the role may not publish there (orgs.forbidden, orgs.folder_restricted). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | The organisation named does not exist, or the account is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | The diagram is already hosted and locked, and the publish names it or files it elsewhere (hosting.diagram_locked; unlock it in the portal first), or several organisations publish their members' diagrams to their library and none is the default (orgs.publish_ambiguous, with details.organizations). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than the size cap, or the diagram renders larger than the plan hosts. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 422 | The diagram source has errors; the diagnostics are in details. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 502 | The diagram could not be stored. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Hosting is not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`PublishAnswer`](https://docs.plotwork.dev/api/schemas.md#publishanswer). | Field | Type | Required | Description | |---|---|---|---| ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # List your hosted diagrams > Page: https://docs.plotwork.dev/api/get-user-diagrams. Index of every page: https://docs.plotwork.dev/llms.txt The account's hosted diagrams, most recently published first and at most 200, with the account's plan, how many diagrams and bytes it holds in total, and the plan's limits. `GET /user/diagrams` ## TL;DR | Question | Answer | |---|---| | What does it do? | List your hosted diagrams. | | What does it take? | Nothing. | | What does it return? | Status 200 with a body of type [`HostedDiagramList`](https://docs.plotwork.dev/api/schemas.md#hosteddiagramlist). | | What can go wrong? | `401` not signed in; `429` too many requests; `503` hosting is not set up on this server. Every failure uses the one error envelope. | ## What it does The account's hosted diagrams, most recently published first and at most 200, with the account's plan, how many diagrams and bytes it holds in total, and the plan's limits. ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The diagrams and the usage. | [`HostedDiagramList`](https://docs.plotwork.dev/api/schemas.md#hosteddiagramlist) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Hosting is not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`HostedDiagramList`](https://docs.plotwork.dev/api/schemas.md#hosteddiagramlist). | Field | Type | Required | Description | |---|---|---|---| | `items` | `HostedDiagram[]` | yes | At most 200, newest published first. | | `items[].id` | `string` | yes | The handle for managing the diagram (delete). It is not in the link. | | `items[].url` | `string` | yes | The public address of the SVG. Anyone with the link can see it; the token in it cannot be guessed. | | `items[].name` | `string \| null` | yes | The label given at publish, or null. | | `items[].folder` | `string \| null` | yes | The folder it is filed in, such as `Payments/Checkout`, or null for the top level. | | `items[].theme` | `"light" \| "dark" \| "auto"` | yes | The color scheme it was drawn in: light, dark, or auto. | | `items[].bytes` | `integer` | yes | Size of the stored SVG in bytes. | | `items[].createdAt` | `integer` | yes | When it was first published, in milliseconds since the epoch. | | `items[].publishedAt` | `integer` | yes | When it was last published, in milliseconds since the epoch. Publishing the same diagram again moves it. | | `items[].expiresAt` | `integer \| null` | yes | When it will be deleted, in milliseconds since the epoch, or null when the plan keeps diagrams until you delete them. | | `items[].locked` | `boolean` | yes | True while the diagram is locked: it cannot be deleted, renamed or moved until you unlock it. A lock does not stop it expiring on a plan that expires diagrams. | | `items[].lockedAt` | `integer \| null` | yes | When it was locked, in milliseconds since the epoch, or null when it is not locked. | | `stableLinks` | `StableLink[]` | yes | Every stable link the account keeps, by alias, including any whose diagram is gone. | | `stableLinks[].id` | `string` | yes | The handle for managing the stable link (delete). It is not in its address. | | `stableLinks[].alias` | `string` | yes | The name you gave it. Only you see it. | | `stableLinks[].url` | `string` | yes | The public address, on the CDN host. It shows the diagram the stable link points at now (cached for up to 60 seconds) and never changes. Anyone with it can see the picture. | | `stableLinks[].diagramId` | `string \| null` | yes | The diagram it shows now, or null when that diagram was deleted or expired; the address then answers 404 until you point the link at a diagram again. | | `stableLinks[].createdAt` | `integer` | yes | When it was made, in milliseconds since the epoch. | | `stableLinks[].updatedAt` | `integer` | yes | When it was last pointed at a diagram, in milliseconds since the epoch. | | `usage` | `HostingUsage` | yes | | | `usage.plan` | `"free" \| "pro"` | yes | The plan the account is on, which decides the limits. | | `usage.count` | `integer` | yes | Hosted diagrams the account holds. This is the total, even when the list is shorter. minimum 0. | | `usage.bytes` | `integer` | yes | Total size of the hosted diagrams in bytes. minimum 0. | | `usage.stableLinks` | `integer` | yes | Stable links the account keeps. minimum 0. | | `usage.limits` | `HostingPlanLimits` | yes | | | `usage.limits.dailyCalls` | `integer` | yes | | | `usage.limits.hostedDiagrams` | `integer` | yes | | | `usage.limits.hostedBytes` | `integer` | yes | | | `usage.limits.retentionDays` | `integer \| null` | yes | Days a diagram is kept after it was last published, or null to keep it until you delete it. | | `usage.limits.maxDiagramBytes` | `integer` | yes | | | `usage.limits.stableLinks` | `integer` | yes | Stable links the account may keep at once. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # File a hosted diagram in a folder > Page: https://docs.plotwork.dev/api/patch-user-diagrams-id. Index of every page: https://docs.plotwork.dev/llms.txt Moves the diagram into a folder (a path such as Payments/Checkout, up to five levels and forty characters a name) or to the top level with null. `PATCH /user/diagrams/{id}` ## TL;DR | Question | Answer | |---|---| | What does it do? | File a hosted diagram in a folder. | | What does it take? | A JSON body of type [`MoveDiagramBody`](https://docs.plotwork.dev/api/schemas.md#movediagrambody). | | What does it return? | Status 204 with no body. | | What can go wrong? | `400` the folder path is not valid; details says why; `401` not signed in; `403` the request came from an origin that may not make changes; `404` no such diagram; `409` the diagram is locked (hosting.diagram_locked); details says when and that you can unlock it; `429` too many requests. Every failure uses the one error envelope. | ## What it does Moves the diagram into a folder (a path such as Payments/Checkout, up to five levels and forty characters a name) or to the top level with null. An invalid path is a 400 with the reason, never rewritten. The link and the expiry do not change. A diagram that is not yours is a 404. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | ## Request body The body has the type [`MoveDiagramBody`](https://docs.plotwork.dev/api/schemas.md#movediagrambody). | Field | Type | Required | Description | |---|---|---|---| | `folder` | `string \| null` | yes | The folder to file the diagram in, or null for the top level. | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Moved. | none | | 400 | The folder path is not valid; details says why. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such diagram. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | The diagram is locked (hosting.diagram_locked); details says when and that you can unlock it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Delete a hosted diagram > Page: https://docs.plotwork.dev/api/delete-user-diagrams-id. Index of every page: https://docs.plotwork.dev/llms.txt Removes the diagram's record, then its file. `DELETE /user/diagrams/{id}` ## TL;DR | Question | Answer | |---|---| | What does it do? | Delete a hosted diagram. | | What does it take? | `id` in the path. | | What does it return? | Status 204 with no body. | | What can go wrong? | `401` not signed in; `403` the request came from an origin that may not make changes; `404` no such diagram; `409` the diagram is locked (hosting.diagram_locked); details says when and that you can unlock it; `429` too many requests; `503` hosting is not set up on this server. Every failure uses the one error envelope. | ## What it does Removes the diagram's record, then its file. A diagram that is not yours is a 404. A locked diagram is a 409 until it is unlocked. The link can keep working for up to an hour from the edge cache. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Deleted. | none | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such diagram. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | The diagram is locked (hosting.diagram_locked); details says when and that you can unlock it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Hosting is not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Lock a hosted diagram > Page: https://docs.plotwork.dev/api/put-user-diagrams-id-lock. Index of every page: https://docs.plotwork.dev/llms.txt Locks the diagram so it cannot be deleted, renamed or moved until you unlock it. `PUT /user/diagrams/{id}/lock` ## TL;DR | Question | Answer | |---|---| | What does it do? | Lock a hosted diagram. | | What does it take? | `id` in the path. | | What does it return? | Status 204 with no body. | | What can go wrong? | `401` not signed in; `403` the request came from an origin that may not make changes; `404` no such diagram; `429` too many requests. Every failure uses the one error envelope. | ## What it does Locks the diagram so it cannot be deleted, renamed or moved until you unlock it. Locking one that is already locked succeeds. A lock does not stop the plan's expiry. A diagram that is not yours is a 404. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Locked. | none | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such diagram. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Unlock a hosted diagram > Page: https://docs.plotwork.dev/api/delete-user-diagrams-id-lock. Index of every page: https://docs.plotwork.dev/llms.txt Removes the lock, so the diagram can be deleted, renamed and moved again. `DELETE /user/diagrams/{id}/lock` ## TL;DR | Question | Answer | |---|---| | What does it do? | Unlock a hosted diagram. | | What does it take? | `id` in the path. | | What does it return? | Status 204 with no body. | | What can go wrong? | `401` not signed in; `403` the request came from an origin that may not make changes; `404` no such diagram; `429` too many requests. Every failure uses the one error envelope. | ## What it does Removes the lock, so the diagram can be deleted, renamed and moved again. Unlocking one that is not locked succeeds. A diagram that is not yours is a 404. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Unlocked. | none | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such diagram. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Save edited source over a hosted diagram > Page: https://docs.plotwork.dev/api/put-user-diagrams-id-source. Index of every page: https://docs.plotwork.dev/llms.txt Draws the source and puts the new picture in place of the diagram's, keeping its id, name, folder and stable links; the picture link changes, so the answer has the new url. `PUT /user/diagrams/{id}/source` ## TL;DR | Question | Answer | |---|---| | What does it do? | Save edited source over a hosted diagram. | | What does it take? | A JSON body of type [`SaveDiagramSourceBody`](https://docs.plotwork.dev/api/schemas.md#savediagramsourcebody). | | What does it return? | Status 200 with a body of type [`EditedDiagram`](https://docs.plotwork.dev/api/schemas.md#editeddiagram). | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` the plan does not include the editor (plan.editor_not_included), the larger picture does not fit the plan (hosting.quota_exceeded), an organisation the account belongs to requires its members to publish to its library (orgs.publish_required), or the request came from an origin that may not make changes; `404` no such diagram; `409` locked (hosting.diagram_locked), saved since (diagram.version_conflict, details.currentversion), or another diagram already has this picture (diagram.duplicate_content); `413` the body is larger than the size cap, or the diagram renders larger than the plan hosts; `422` the diagram source has errors; the diagnostics are in details; `429` too many requests; `502` the diagram could not be stored; `503` hosting is not set up on this server. Every failure uses the one error envelope. | ## What it does Draws the source and puts the new picture in place of the diagram's, keeping its id, name, folder and stable links; the picture link changes, so the answer has the new url. The replaced picture is kept as a previous version. baseVersion is the publishedAt the editor loaded: a diagram saved since is 409 diagram.version_conflict with details.currentVersion. Needs a plan with the diagram editor (403 plan.editor_not_included). ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | ## Request body The body has the type [`SaveDiagramSourceBody`](https://docs.plotwork.dev/api/schemas.md#savediagramsourcebody). | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | Diagram source; the first line is the header, such as flow "Title" (draw_diagram lists every kind). at least 1 character; at most 50000 characters. | | `theme` | `"light" \| "dark" \| "auto"` | no | The color scheme to draw it in: light, dark, or auto. Left out, the theme the diagram has. | | `baseVersion` | `integer` | yes | The diagram's publishedAt (milliseconds since the epoch) when the editor loaded it. If the diagram was saved since, the answer is 409 diagram.version_conflict with details.currentVersion. minimum 0. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The diagram as the list shows it, with warnings and changed. | [`EditedDiagram`](https://docs.plotwork.dev/api/schemas.md#editeddiagram) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The plan does not include the editor (plan.editor_not_included), the larger picture does not fit the plan (hosting.quota_exceeded), an organisation the account belongs to requires its members to publish to its library (orgs.publish_required), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such diagram. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | Locked (hosting.diagram_locked), saved since (diagram.version_conflict, details.currentVersion), or another diagram already has this picture (diagram.duplicate_content). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than the size cap, or the diagram renders larger than the plan hosts. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 422 | The diagram source has errors; the diagnostics are in details. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 502 | The diagram could not be stored. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Hosting is not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`EditedDiagram`](https://docs.plotwork.dev/api/schemas.md#editeddiagram). | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The handle for managing the diagram (delete). It is not in the link. | | `url` | `string` | yes | The public address of the SVG. Anyone with the link can see it; the token in it cannot be guessed. | | `name` | `string \| null` | yes | The label given at publish, or null. | | `folder` | `string \| null` | yes | The folder it is filed in, such as `Payments/Checkout`, or null for the top level. | | `theme` | `"light" \| "dark" \| "auto"` | yes | The color scheme it was drawn in: light, dark, or auto. | | `bytes` | `integer` | yes | Size of the stored SVG in bytes. | | `createdAt` | `integer` | yes | When it was first published, in milliseconds since the epoch. | | `publishedAt` | `integer` | yes | When it was last published, in milliseconds since the epoch. Publishing the same diagram again moves it. | | `expiresAt` | `integer \| null` | yes | When it will be deleted, in milliseconds since the epoch, or null when the plan keeps diagrams until you delete them. | | `locked` | `boolean` | yes | True while the diagram is locked: it cannot be deleted, renamed or moved until you unlock it. A lock does not stop it expiring on a plan that expires diagrams. | | `lockedAt` | `integer \| null` | yes | When it was locked, in milliseconds since the epoch, or null when it is not locked. | | `warnings` | `Diagnostic[]` | yes | Quality warnings from drawing the source, as draw_diagram gives them. Empty for a restore. | | `warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `changed` | `boolean` | yes | False when the source draws the picture the diagram already has: nothing was stored and no version was kept. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # List a hosted diagram's previous versions > Page: https://docs.plotwork.dev/api/get-user-diagrams-id-versions. Index of every page: https://docs.plotwork.dev/llms.txt The pictures the diagram had before it was edited or restored in the portal, newest first, at most ten. `GET /user/diagrams/{id}/versions` ## TL;DR | Question | Answer | |---|---| | What does it do? | List a hosted diagram's previous versions. | | What does it take? | `id` in the path. | | What does it return? | Status 200 with a body of type [`DiagramVersionList`](https://docs.plotwork.dev/api/schemas.md#diagramversionlist). | | What can go wrong? | `401` not signed in; `404` no such diagram; `429` too many requests; `503` hosting is not set up on this server. Every failure uses the one error envelope. | ## What it does The pictures the diagram had before it was edited or restored in the portal, newest first, at most ten. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The versions. | [`DiagramVersionList`](https://docs.plotwork.dev/api/schemas.md#diagramversionlist) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such diagram. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Hosting is not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`DiagramVersionList`](https://docs.plotwork.dev/api/schemas.md#diagramversionlist). | Field | Type | Required | Description | |---|---|---|---| | `items` | `DiagramVersion[]` | yes | Newest first. A diagram keeps at most ten. | | `items[].id` | `string` | yes | The handle for restoring it. | | `items[].savedAt` | `integer` | yes | When it stopped being the current picture, in milliseconds since the epoch. | | `items[].bytes` | `integer` | yes | Size of the stored SVG in bytes. Versions do not count toward the plan. | | `items[].theme` | `"light" \| "dark" \| "auto"` | yes | | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Restore a previous version of a hosted diagram > Page: https://docs.plotwork.dev/api/post-user-diagrams-id-versions-version-id-restore. Index of every page: https://docs.plotwork.dev/llms.txt Makes the version the diagram's picture, with the checks and the answer of a save. `POST /user/diagrams/{id}/versions/{versionId}/restore` ## TL;DR | Question | Answer | |---|---| | What does it do? | Restore a previous version of a hosted diagram. | | What does it take? | A JSON body of type [`RestoreDiagramVersionBody`](https://docs.plotwork.dev/api/schemas.md#restorediagramversionbody). | | What does it return? | Status 200 with a body of type [`EditedDiagram`](https://docs.plotwork.dev/api/schemas.md#editeddiagram). | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` the plan does not include the editor (plan.editor_not_included), the picture does not fit the plan (hosting.quota_exceeded), an organisation the account belongs to requires its members to publish to its library (orgs.publish_required), or the request came from an origin that may not make changes; `404` no such diagram or version; `409` locked (hosting.diagram_locked), saved since (diagram.version_conflict), or another diagram already has this picture (diagram.duplicate_content); `429` too many requests; `502` the diagram could not be stored; `503` hosting is not set up on this server. Every failure uses the one error envelope. | ## What it does Makes the version the diagram's picture, with the checks and the answer of a save. The current picture becomes a version and the picture link changes. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `versionId` | `string` | yes | | ## Request body The body has the type [`RestoreDiagramVersionBody`](https://docs.plotwork.dev/api/schemas.md#restorediagramversionbody). | Field | Type | Required | Description | |---|---|---|---| | `baseVersion` | `integer` | yes | The diagram's publishedAt (milliseconds since the epoch) when the editor loaded it. If the diagram was saved since, the answer is 409 diagram.version_conflict with details.currentVersion. minimum 0. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The diagram as the list shows it. | [`EditedDiagram`](https://docs.plotwork.dev/api/schemas.md#editeddiagram) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The plan does not include the editor (plan.editor_not_included), the picture does not fit the plan (hosting.quota_exceeded), an organisation the account belongs to requires its members to publish to its library (orgs.publish_required), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such diagram or version. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | Locked (hosting.diagram_locked), saved since (diagram.version_conflict), or another diagram already has this picture (diagram.duplicate_content). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 502 | The diagram could not be stored. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Hosting is not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`EditedDiagram`](https://docs.plotwork.dev/api/schemas.md#editeddiagram). | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The handle for managing the diagram (delete). It is not in the link. | | `url` | `string` | yes | The public address of the SVG. Anyone with the link can see it; the token in it cannot be guessed. | | `name` | `string \| null` | yes | The label given at publish, or null. | | `folder` | `string \| null` | yes | The folder it is filed in, such as `Payments/Checkout`, or null for the top level. | | `theme` | `"light" \| "dark" \| "auto"` | yes | The color scheme it was drawn in: light, dark, or auto. | | `bytes` | `integer` | yes | Size of the stored SVG in bytes. | | `createdAt` | `integer` | yes | When it was first published, in milliseconds since the epoch. | | `publishedAt` | `integer` | yes | When it was last published, in milliseconds since the epoch. Publishing the same diagram again moves it. | | `expiresAt` | `integer \| null` | yes | When it will be deleted, in milliseconds since the epoch, or null when the plan keeps diagrams until you delete them. | | `locked` | `boolean` | yes | True while the diagram is locked: it cannot be deleted, renamed or moved until you unlock it. A lock does not stop it expiring on a plan that expires diagrams. | | `lockedAt` | `integer \| null` | yes | When it was locked, in milliseconds since the epoch, or null when it is not locked. | | `warnings` | `Diagnostic[]` | yes | Quality warnings from drawing the source, as draw_diagram gives them. Empty for a restore. | | `warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `changed` | `boolean` | yes | False when the source draws the picture the diagram already has: nothing was stored and no version was kept. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Point a stable link at one of your diagrams > Page: https://docs.plotwork.dev/api/post-user-stable-links. Index of every page: https://docs.plotwork.dev/llms.txt Points the stable link with this alias at one of your hosted diagrams, making it when you have none of that name. `POST /user/stable-links` ## TL;DR | Question | Answer | |---|---| | What does it do? | Point a stable link at one of your diagrams. | | What does it take? | A JSON body of type [`SetStableLinkBody`](https://docs.plotwork.dev/api/schemas.md#setstablelinkbody). | | What does it return? | Status 200 with a body of type [`StableLink`](https://docs.plotwork.dev/api/schemas.md#stablelink). | | What can go wrong? | `400` the alias or the diagram id is not valid; details says why; `401` not signed in; `403` the plan has no room for another stable link (hosting.quota_exceeded, reason stablelinks), an organisation the account belongs to requires its members to publish to its library (orgs.publish_required, with details.organizations), or the request came from an origin that may not make changes; `404` no such diagram; `429` too many requests; `502` the picture could not be copied to the stable link; `503` hosting is not set up on this server. Every failure uses the one error envelope. | ## What it does Points the stable link with this alias at one of your hosted diagrams, making it when you have none of that name. Its address, on the CDN host, always shows the diagram it points at now (cached for 60 seconds) and never changes when the link is moved. How many stable links an account keeps depends on its plan. A diagram that is not yours is a 404. ## Request body The body has the type [`SetStableLinkBody`](https://docs.plotwork.dev/api/schemas.md#setstablelinkbody). | Field | Type | Required | Description | |---|---|---|---| | `alias` | `string` | yes | The name of a stable link: lower-case letters and digits in words joined by single hyphens, up to 60 characters. Only you see it; the public address carries a random token instead. at least 1 character; at most 60 characters; pattern `^[a-z0-9]+(?:-[a-z0-9]+)*$`. | | `diagramId` | `string` | yes | One of your hosted diagrams, by its id. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The stable link was moved. | [`StableLink`](https://docs.plotwork.dev/api/schemas.md#stablelink) | | 201 | The stable link was made. | [`StableLink`](https://docs.plotwork.dev/api/schemas.md#stablelink) | | 400 | The alias or the diagram id is not valid; details says why. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The plan has no room for another stable link (hosting.quota_exceeded, reason stableLinks), an organisation the account belongs to requires its members to publish to its library (orgs.publish_required, with details.organizations), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such diagram. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 502 | The picture could not be copied to the stable link. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Hosting is not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`StableLink`](https://docs.plotwork.dev/api/schemas.md#stablelink). | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The handle for managing the stable link (delete). It is not in its address. | | `alias` | `string` | yes | The name you gave it. Only you see it. | | `url` | `string` | yes | The public address, on the CDN host. It shows the diagram the stable link points at now (cached for up to 60 seconds) and never changes. Anyone with it can see the picture. | | `diagramId` | `string \| null` | yes | The diagram it shows now, or null when that diagram was deleted or expired; the address then answers 404 until you point the link at a diagram again. | | `createdAt` | `integer` | yes | When it was made, in milliseconds since the epoch. | | `updatedAt` | `integer` | yes | When it was last pointed at a diagram, in milliseconds since the epoch. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Delete a stable link > Page: https://docs.plotwork.dev/api/delete-user-stable-links-id. Index of every page: https://docs.plotwork.dev/llms.txt Deletes the stable link, so its address answers 404 from then on (a cache may keep the copy for up to a minute). `DELETE /user/stable-links/{id}` ## TL;DR | Question | Answer | |---|---| | What does it do? | Delete a stable link. | | What does it take? | `id` in the path. | | What does it return? | Status 204 with no body. | | What can go wrong? | `401` not signed in; `403` the request came from an origin that may not make changes; `404` no such stable link; `429` too many requests; `503` hosting is not set up on this server. Every failure uses the one error envelope. | ## What it does Deletes the stable link, so its address answers 404 from then on (a cache may keep the copy for up to a minute). The diagram it pointed at is not touched. One that is not yours is a 404. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Deleted. | none | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such stable link. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Hosting is not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Describe a diagram link for an embedder (oEmbed) > Page: https://docs.plotwork.dev/api/get-oembed. Index of every page: https://docs.plotwork.dev/llms.txt The oEmbed endpoint for Notion, Slack, wikis and other embedders. `GET /oembed` ## TL;DR | Question | Answer | |---|---| | What does it do? | Describe a diagram link for an embedder (oEmbed). | | What does it take? | `url`, `format`, `maxwidth`, `maxheight` in the query string. | | What does it return? | Status 200 with a body of type [`OEmbedPhoto`](https://docs.plotwork.dev/api/schemas.md#oembedphoto). | | What can go wrong? | `400` the query is not valid; `404` the address is not a plotwork diagram link, or it shows nothing now; `429` too many requests; `501` a format other than json; only json is answered; `503` hosting is not set up on this server. Every failure uses the one error envelope. | ## What it does The oEmbed endpoint for Notion, Slack, wikis and other embedders. Give it a stable link, a picture link, or a viewer page, and it answers a photo: the public SVG, its size (scaled to maxwidth and maxheight), the title "Diagram" and no author. For a stable link the picture is the stable link itself, so the embed keeps showing the latest version. Only format=json; another format is a 501. An address that is not a Plotwork diagram link, or shows nothing now, is a 404. ## Query parameters | Field | Type | Required | Description | |---|---|---|---| | `url` | `string` | yes | A stable link, a picture link, or the viewer page of a picture. | | `format` | `string` | no | Only `json` is answered; any other format is a 501, as the specification asks. | | `maxwidth` | `integer` | no | The widest the picture may be, in pixels. | | `maxheight` | `integer` | no | The tallest the picture may be, in pixels. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The oEmbed answer. | [`OEmbedPhoto`](https://docs.plotwork.dev/api/schemas.md#oembedphoto) | | 400 | The query is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | The address is not a Plotwork diagram link, or it shows nothing now. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 501 | A format other than json; only json is answered. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Hosting is not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`OEmbedPhoto`](https://docs.plotwork.dev/api/schemas.md#oembedphoto). | Field | Type | Required | Description | |---|---|---|---| | `version` | `"1.0"` | yes | | | `type` | `"photo"` | yes | | | `provider_name` | `string` | yes | | | `provider_url` | `string` | yes | | | `title` | `string` | yes | Always "Diagram": the diagram's own name is shown only to its owner. | | `url` | `string` | yes | The picture. For a stable link, the stable link itself, so the embed keeps showing the latest version. | | `width` | `integer` | yes | | | `height` | `integer` | yes | | | `cache_age` | `integer` | yes | Seconds the answer may be kept: 60 for a stable link, 3600 for a picture. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # List your organisations > Page: https://docs.plotwork.dev/api/get-orgs. Index of every page: https://docs.plotwork.dev/llms.txt The organisations the account belongs to, by name, at most 100, each with the caller's role, its plan (its owner's), what it holds, and the limits. `GET /orgs` ## TL;DR | Question | Answer | |---|---| | What does it do? | List your organisations. | | What does it take? | Nothing. | | What does it return? | Status 200 with a body of type [`OrganisationList`](https://docs.plotwork.dev/api/schemas.md#organisationlist). | | What can go wrong? | `401` not signed in; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does The organisations the account belongs to, by name, at most 100, each with the caller's role, its plan (its owner's), what it holds, and the limits. ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The organisations. | [`OrganisationList`](https://docs.plotwork.dev/api/schemas.md#organisationlist) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`OrganisationList`](https://docs.plotwork.dev/api/schemas.md#organisationlist). | Field | Type | Required | Description | |---|---|---|---| | `items` | `Organisation[]` | yes | By name, at most 100. | | `items[].id` | `string` | yes | | | `items[].slug` | `string` | yes | | | `items[].name` | `string` | yes | | | `items[].role` | `"owner" \| "admin" \| "member" \| "reader"` | yes | What the caller is in this organisation. | | `items[].createdAt` | `integer` | yes | Milliseconds since the epoch. | | `items[].memberCount` | `integer` | yes | People who have joined, the owner included. The same number as `usage.members`. minimum 0. | | `items[].plan` | `"free" \| "pro" \| "enterprise"` | yes | The organisation's plan: `enterprise` under a contract, otherwise its owner's. | | `items[].contractEndsOn` | `string \| null` | yes | The last day of the Enterprise contract, `YYYY-MM-DD`, or null when there is none or it has no end date written. The terms of a contract are not shown to members. | | `items[].usage` | `OrganisationUsage` | yes | | | `items[].usage.diagrams` | `integer` | yes | minimum 0. | | `items[].usage.bytes` | `integer` | yes | minimum 0. | | `items[].usage.members` | `integer` | yes | People who have joined, the owner included. minimum 0. | | `items[].usage.invites` | `integer` | yes | Invitations that can still be accepted. They count toward the member limit. minimum 0. | | `items[].limits` | `OrganisationLimits` | yes | | | `items[].limits.dailyCalls` | `integer` | yes | | | `items[].limits.hostedDiagrams` | `integer` | yes | Diagrams the organisation may hold. | | `items[].limits.hostedBytes` | `integer` | yes | Total bytes of diagrams the organisation may hold. | | `items[].limits.retentionDays` | `integer \| null` | yes | Days a diagram is kept after it was last published, or null to keep it until it is deleted. | | `items[].limits.maxDiagramBytes` | `integer` | yes | | | `items[].limits.stableLinks` | `integer` | yes | Stable links the owner may keep for their own diagrams. An organisation's diagrams have none. | | `items[].limits.organisations` | `integer` | yes | Organisations an account on this plan may own. | | `items[].limits.orgMembers` | `integer` | yes | People one organisation may have, the owner included and pending invitations counted. | | `items[].limits.orgIcons` | `integer` | yes | Custom icons one organisation may keep; zero on a plan without custom icons. minimum 0. | | `items[].limits.driftHistoryDays` | `integer` | yes | Days the drift dashboard keeps a run and a status after it was reported; zero on a plan without the dashboard. minimum 0. | | `items[].limits.auditRetentionDays` | `integer` | yes | Days the organisation's audit log keeps an event; zero on a plan that records none. minimum 0. | | `items[].avatar` | `OrgAvatar` | yes | The organisation's uploaded avatar, or null when it has none and a mark made from its id stands in. | | `items[].avatar.version` | `string` | yes | Changes with every upload. Put it in the address (?v=) so a new picture is a new address. | | `items[].avatar.contentType` | `"image/svg+xml" \| "image/png" \| "image/gif" \| "image/webp"` | yes | | | `items[].avatar.animated` | `boolean` | yes | True when it moves, so a page can hold it still for a person who asked for less motion. | | `items[].keepEditable` | `boolean` | yes | The owner's setting "Keep diagrams editable": true when a publish to the organisation that does not send embedSource writes the diagram source into the picture, so members can edit it in the portal later. Every member can open such a picture and read its source. | | `items[].publishing` | `OrganisationPublishing` | yes | | | `items[].publishing.policy` | `"personal" \| "organisation-default" \| "organisation-required"` | yes | What the owner or an admin chose: personal (members publish where they choose), organisation-default (a member's publish that names no organisation goes to this library), or organisation-required (nothing a member publishes reaches a public link). | | `items[].publishing.effective` | `"personal" \| "organisation-default" \| "organisation-required"` | yes | What is enforced now: the choice, except organisation-required on a plan that no longer includes it, which acts as organisation-default. | | `items[].publishing.hideViewLinks` | `boolean` | yes | Whether hosted answers leave view links out for members and the organisation's keys. Honoured only while effective is organisation-required. | | `items[].publishing.requiredAvailable` | `boolean` | yes | Whether the organisation's plan includes organisation-required (Pro and Enterprise). | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Create an organisation > Page: https://docs.plotwork.dev/api/post-orgs. Index of every page: https://docs.plotwork.dev/llms.txt Makes an organisation owned by the account. `POST /orgs` ## TL;DR | Question | Answer | |---|---| | What does it do? | Create an organisation. | | What does it take? | A JSON body of type [`CreateOrganisationBody`](https://docs.plotwork.dev/api/schemas.md#createorganisationbody). | | What does it return? | Status 201 with a body of type [`Organisation`](https://docs.plotwork.dev/api/schemas.md#organisation). | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` the account already owns the most organisations its plan allows (orgs.limit_reached), or the request came from an origin that may not make changes; `409` the slug is taken (orgs.slug_taken); `422` the name, or the slug, holds a word that is not allowed in a name (orgs.name_not_allowed); `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Makes an organisation owned by the account. The slug is chosen here and never changes; left out, it is made from the name. The account owns as many as its plan allows: 403 orgs.limit_reached says the plan, the limit, and what is used. 409 orgs.slug_taken for a slug someone has. ## Request body The body has the type [`CreateOrganisationBody`](https://docs.plotwork.dev/api/schemas.md#createorganisationbody). | Field | Type | Required | Description | |---|---|---|---| | `name` | `string` | yes | What people read. 1 to 60 characters. It can be changed; the slug cannot. at least 1 character; pattern `^[^\p{Cc}]+$/u`. | | `slug` | `string` | no | The handle. Left out, it is made from the name, with a short suffix when that is taken. at least 3 characters; at most 40 characters; pattern `^[a-z0-9][a-z0-9-]*[a-z0-9]$`. | ## Responses | Status | Meaning | Body | |---|---|---| | 201 | The organisation. | [`Organisation`](https://docs.plotwork.dev/api/schemas.md#organisation) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The account already owns the most organisations its plan allows (orgs.limit_reached), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | The slug is taken (orgs.slug_taken). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 422 | The name, or the slug, holds a word that is not allowed in a name (orgs.name_not_allowed). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 201 answer has the type [`Organisation`](https://docs.plotwork.dev/api/schemas.md#organisation). | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `slug` | `string` | yes | | | `name` | `string` | yes | | | `role` | `"owner" \| "admin" \| "member" \| "reader"` | yes | What the caller is in this organisation. | | `createdAt` | `integer` | yes | Milliseconds since the epoch. | | `memberCount` | `integer` | yes | People who have joined, the owner included. The same number as `usage.members`. minimum 0. | | `plan` | `"free" \| "pro" \| "enterprise"` | yes | The organisation's plan: `enterprise` under a contract, otherwise its owner's. | | `contractEndsOn` | `string \| null` | yes | The last day of the Enterprise contract, `YYYY-MM-DD`, or null when there is none or it has no end date written. The terms of a contract are not shown to members. | | `usage` | `OrganisationUsage` | yes | | | `usage.diagrams` | `integer` | yes | minimum 0. | | `usage.bytes` | `integer` | yes | minimum 0. | | `usage.members` | `integer` | yes | People who have joined, the owner included. minimum 0. | | `usage.invites` | `integer` | yes | Invitations that can still be accepted. They count toward the member limit. minimum 0. | | `limits` | `OrganisationLimits` | yes | | | `limits.dailyCalls` | `integer` | yes | | | `limits.hostedDiagrams` | `integer` | yes | Diagrams the organisation may hold. | | `limits.hostedBytes` | `integer` | yes | Total bytes of diagrams the organisation may hold. | | `limits.retentionDays` | `integer \| null` | yes | Days a diagram is kept after it was last published, or null to keep it until it is deleted. | | `limits.maxDiagramBytes` | `integer` | yes | | | `limits.stableLinks` | `integer` | yes | Stable links the owner may keep for their own diagrams. An organisation's diagrams have none. | | `limits.organisations` | `integer` | yes | Organisations an account on this plan may own. | | `limits.orgMembers` | `integer` | yes | People one organisation may have, the owner included and pending invitations counted. | | `limits.orgIcons` | `integer` | yes | Custom icons one organisation may keep; zero on a plan without custom icons. minimum 0. | | `limits.driftHistoryDays` | `integer` | yes | Days the drift dashboard keeps a run and a status after it was reported; zero on a plan without the dashboard. minimum 0. | | `limits.auditRetentionDays` | `integer` | yes | Days the organisation's audit log keeps an event; zero on a plan that records none. minimum 0. | | `avatar` | `OrgAvatar` | yes | The organisation's uploaded avatar, or null when it has none and a mark made from its id stands in. | | `avatar.version` | `string` | yes | Changes with every upload. Put it in the address (?v=) so a new picture is a new address. | | `avatar.contentType` | `"image/svg+xml" \| "image/png" \| "image/gif" \| "image/webp"` | yes | | | `avatar.animated` | `boolean` | yes | True when it moves, so a page can hold it still for a person who asked for less motion. | | `keepEditable` | `boolean` | yes | The owner's setting "Keep diagrams editable": true when a publish to the organisation that does not send embedSource writes the diagram source into the picture, so members can edit it in the portal later. Every member can open such a picture and read its source. | | `publishing` | `OrganisationPublishing` | yes | | | `publishing.policy` | `"personal" \| "organisation-default" \| "organisation-required"` | yes | What the owner or an admin chose: personal (members publish where they choose), organisation-default (a member's publish that names no organisation goes to this library), or organisation-required (nothing a member publishes reaches a public link). | | `publishing.effective` | `"personal" \| "organisation-default" \| "organisation-required"` | yes | What is enforced now: the choice, except organisation-required on a plan that no longer includes it, which acts as organisation-default. | | `publishing.hideViewLinks` | `boolean` | yes | Whether hosted answers leave view links out for members and the organisation's keys. Honoured only while effective is organisation-required. | | `publishing.requiredAvailable` | `boolean` | yes | Whether the organisation's plan includes organisation-required (Pro and Enterprise). | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Read an organisation > Page: https://docs.plotwork.dev/api/get-orgs-org. Index of every page: https://docs.plotwork.dev/llms.txt The organisation, the caller's role, its plan (its owner's), what it holds, and the limits. `GET /orgs/{org}` ## TL;DR | Question | Answer | |---|---| | What does it do? | Read an organisation. | | What does it take? | `org` in the path. | | What does it return? | Status 200 with a body of type [`Organisation`](https://docs.plotwork.dev/api/schemas.md#organisation). | | What can go wrong? | `401` not signed in; `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does The organisation, the caller's role, its plan (its owner's), what it holds, and the limits. A person who is not a member gets 404, the same as for an organisation that does not exist. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The organisation. | [`Organisation`](https://docs.plotwork.dev/api/schemas.md#organisation) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`Organisation`](https://docs.plotwork.dev/api/schemas.md#organisation). | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `slug` | `string` | yes | | | `name` | `string` | yes | | | `role` | `"owner" \| "admin" \| "member" \| "reader"` | yes | What the caller is in this organisation. | | `createdAt` | `integer` | yes | Milliseconds since the epoch. | | `memberCount` | `integer` | yes | People who have joined, the owner included. The same number as `usage.members`. minimum 0. | | `plan` | `"free" \| "pro" \| "enterprise"` | yes | The organisation's plan: `enterprise` under a contract, otherwise its owner's. | | `contractEndsOn` | `string \| null` | yes | The last day of the Enterprise contract, `YYYY-MM-DD`, or null when there is none or it has no end date written. The terms of a contract are not shown to members. | | `usage` | `OrganisationUsage` | yes | | | `usage.diagrams` | `integer` | yes | minimum 0. | | `usage.bytes` | `integer` | yes | minimum 0. | | `usage.members` | `integer` | yes | People who have joined, the owner included. minimum 0. | | `usage.invites` | `integer` | yes | Invitations that can still be accepted. They count toward the member limit. minimum 0. | | `limits` | `OrganisationLimits` | yes | | | `limits.dailyCalls` | `integer` | yes | | | `limits.hostedDiagrams` | `integer` | yes | Diagrams the organisation may hold. | | `limits.hostedBytes` | `integer` | yes | Total bytes of diagrams the organisation may hold. | | `limits.retentionDays` | `integer \| null` | yes | Days a diagram is kept after it was last published, or null to keep it until it is deleted. | | `limits.maxDiagramBytes` | `integer` | yes | | | `limits.stableLinks` | `integer` | yes | Stable links the owner may keep for their own diagrams. An organisation's diagrams have none. | | `limits.organisations` | `integer` | yes | Organisations an account on this plan may own. | | `limits.orgMembers` | `integer` | yes | People one organisation may have, the owner included and pending invitations counted. | | `limits.orgIcons` | `integer` | yes | Custom icons one organisation may keep; zero on a plan without custom icons. minimum 0. | | `limits.driftHistoryDays` | `integer` | yes | Days the drift dashboard keeps a run and a status after it was reported; zero on a plan without the dashboard. minimum 0. | | `limits.auditRetentionDays` | `integer` | yes | Days the organisation's audit log keeps an event; zero on a plan that records none. minimum 0. | | `avatar` | `OrgAvatar` | yes | The organisation's uploaded avatar, or null when it has none and a mark made from its id stands in. | | `avatar.version` | `string` | yes | Changes with every upload. Put it in the address (?v=) so a new picture is a new address. | | `avatar.contentType` | `"image/svg+xml" \| "image/png" \| "image/gif" \| "image/webp"` | yes | | | `avatar.animated` | `boolean` | yes | True when it moves, so a page can hold it still for a person who asked for less motion. | | `keepEditable` | `boolean` | yes | The owner's setting "Keep diagrams editable": true when a publish to the organisation that does not send embedSource writes the diagram source into the picture, so members can edit it in the portal later. Every member can open such a picture and read its source. | | `publishing` | `OrganisationPublishing` | yes | | | `publishing.policy` | `"personal" \| "organisation-default" \| "organisation-required"` | yes | What the owner or an admin chose: personal (members publish where they choose), organisation-default (a member's publish that names no organisation goes to this library), or organisation-required (nothing a member publishes reaches a public link). | | `publishing.effective` | `"personal" \| "organisation-default" \| "organisation-required"` | yes | What is enforced now: the choice, except organisation-required on a plan that no longer includes it, which acts as organisation-default. | | `publishing.hideViewLinks` | `boolean` | yes | Whether hosted answers leave view links out for members and the organisation's keys. Honoured only while effective is organisation-required. | | `publishing.requiredAvailable` | `boolean` | yes | Whether the organisation's plan includes organisation-required (Pro and Enterprise). | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Rename an organisation > Page: https://docs.plotwork.dev/api/patch-orgs-org. Index of every page: https://docs.plotwork.dev/llms.txt Changes the name. `PATCH /orgs/{org}` ## TL;DR | Question | Answer | |---|---| | What does it do? | Rename an organisation. | | What does it take? | A JSON body of type [`RenameOrganisationBody`](https://docs.plotwork.dev/api/schemas.md#renameorganisationbody). | | What does it return? | Status 204 with no body. | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` the caller's role may not rename (orgs.forbidden), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `422` the name holds a word that is not allowed in a name (orgs.name_not_allowed); `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Changes the name. The owner and an admin may. The slug never changes. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Request body The body has the type [`RenameOrganisationBody`](https://docs.plotwork.dev/api/schemas.md#renameorganisationbody). | Field | Type | Required | Description | |---|---|---|---| | `name` | `string` | yes | What people read. 1 to 60 characters. It can be changed; the slug cannot. at least 1 character; pattern `^[^\p{Cc}]+$/u`. | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Renamed. | none | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The caller's role may not rename (orgs.forbidden), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 422 | The name holds a word that is not allowed in a name (orgs.name_not_allowed). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Delete an organisation > Page: https://docs.plotwork.dev/api/delete-orgs-org. Index of every page: https://docs.plotwork.dev/llms.txt Removes every diagram file from the private bucket, then the rows. `DELETE /orgs/{org}` ## TL;DR | Question | Answer | |---|---| | What does it do? | Delete an organisation. | | What does it take? | `org` in the path. | | What does it return? | Status 204 with no body. | | What can go wrong? | `401` not signed in; `403` only the owner may delete (orgs.forbidden), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `429` too many requests; `502` a file could not be removed; try again; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Removes every diagram file from the private bucket, then the rows. Only the owner may. If a file cannot be removed the organisation stays and the owner can try again. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Deleted. | none | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | Only the owner may delete (orgs.forbidden), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 502 | A file could not be removed; try again. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # List an organisation's members > Page: https://docs.plotwork.dev/api/get-orgs-org-members. Index of every page: https://docs.plotwork.dev/llms.txt Each member's account id, email address, role, and join date. `GET /orgs/{org}/members` ## TL;DR | Question | Answer | |---|---| | What does it do? | List an organisation's members. | | What does it take? | `org` in the path. | | What does it return? | Status 200 with a body of type [`OrganisationMemberList`](https://docs.plotwork.dev/api/schemas.md#organisationmemberlist). | | What can go wrong? | `401` not signed in; `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Each member's account id, email address, role, and join date. Every member sees the addresses: a team knows who is in it. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The members. | [`OrganisationMemberList`](https://docs.plotwork.dev/api/schemas.md#organisationmemberlist) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`OrganisationMemberList`](https://docs.plotwork.dev/api/schemas.md#organisationmemberlist). | Field | Type | Required | Description | |---|---|---|---| | `items` | `OrganisationMember[]` | yes | | | `items[].userId` | `string` | yes | | | `items[].email` | `string` | yes | Shown to every member: a team knows who is in it. | | `items[].role` | `"owner" \| "admin" \| "member" \| "reader"` | yes | | | `items[].joinedAt` | `integer` | yes | Milliseconds since the epoch. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Change a member's role > Page: https://docs.plotwork.dev/api/patch-orgs-org-members-user-id. Index of every page: https://docs.plotwork.dev/llms.txt Sets a member's role to admin, member or reader. `PATCH /orgs/{org}/members/{userId}` ## TL;DR | Question | Answer | |---|---| | What does it do? | Change a member's role. | | What does it take? | A JSON body of type [`ChangeMemberRoleBody`](https://docs.plotwork.dev/api/schemas.md#changememberrolebody). | | What does it return? | Status 204 with no body. | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` only the owner may change a role (orgs.forbidden), or the request came from an origin that may not make changes; `404` no such organisation or member, or the caller is not a member of the organisation; `409` the owner's row cannot be changed (orgs.owner_locked); `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Sets a member's role to admin, member or reader. Only the owner may. The owner's own row cannot be changed (409 orgs.owner_locked). ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | | `userId` | `string` | yes | The member's account id. | ## Request body The body has the type [`ChangeMemberRoleBody`](https://docs.plotwork.dev/api/schemas.md#changememberrolebody). | Field | Type | Required | Description | |---|---|---|---| | `role` | `"admin" \| "member" \| "reader"` | yes | `admin`, `member` or `reader`. An admin can invite and remove members and readers, rename the organisation and manage its keys; a member can publish and change their own diagrams; a reader can view and download and change nothing. Only the owner offers `admin` or changes a role. | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Changed. | none | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | Only the owner may change a role (orgs.forbidden), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation or member, or the caller is not a member of the organisation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | The owner's row cannot be changed (orgs.owner_locked). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Remove a member, or leave > Page: https://docs.plotwork.dev/api/delete-orgs-org-members-user-id. Index of every page: https://docs.plotwork.dev/llms.txt Removes a member, or leaves when userId is the caller's own. `DELETE /orgs/{org}/members/{userId}` ## TL;DR | Question | Answer | |---|---| | What does it do? | Remove a member, or leave. | | What does it take? | `org`, `userId` in the path. | | What does it return? | Status 204 with no body. | | What can go wrong? | `401` not signed in; `403` the caller's role may not remove this member (orgs.forbidden), or the request came from an origin that may not make changes; `404` no such organisation or member, or the caller is not a member of the organisation; `409` the owner cannot be removed or leave (orgs.owner_locked); `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Removes a member, or leaves when userId is the caller's own. The owner removes anyone, an admin removes members and readers only, and a member or a reader removes nobody. The owner cannot leave (409 orgs.owner_locked). The diagrams a person published stay, and the person is taken off every folder rule. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | | `userId` | `string` | yes | The member's account id. | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Removed. | none | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The caller's role may not remove this member (orgs.forbidden), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation or member, or the caller is not a member of the organisation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | The owner cannot be removed or leave (orgs.owner_locked). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # List an organisation's pending invitations > Page: https://docs.plotwork.dev/api/get-orgs-org-invites. Index of every page: https://docs.plotwork.dev/llms.txt The invitations that can still be accepted. `GET /orgs/{org}/invites` ## TL;DR | Question | Answer | |---|---| | What does it do? | List an organisation's pending invitations. | | What does it take? | `org` in the path. | | What does it return? | Status 200 with a body of type [`OrganisationInviteList`](https://docs.plotwork.dev/api/schemas.md#organisationinvitelist). | | What can go wrong? | `401` not signed in; `403` a member may not see invitations (orgs.forbidden); `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does The invitations that can still be accepted. The token is never returned. The owner and an admin may look. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The pending invitations. | [`OrganisationInviteList`](https://docs.plotwork.dev/api/schemas.md#organisationinvitelist) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A member may not see invitations (orgs.forbidden). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`OrganisationInviteList`](https://docs.plotwork.dev/api/schemas.md#organisationinvitelist). | Field | Type | Required | Description | |---|---|---|---| | `items` | `OrganisationInvite[]` | yes | | | `items[].id` | `string` | yes | | | `items[].email` | `string` | yes | | | `items[].role` | `"admin" \| "member" \| "reader"` | yes | | | `items[].createdAt` | `integer` | yes | Milliseconds since the epoch. | | `items[].expiresAt` | `integer` | yes | Milliseconds since the epoch. Seven days after it was made. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Invite an address to an organisation > Page: https://docs.plotwork.dev/api/post-orgs-org-invites. Index of every page: https://docs.plotwork.dev/llms.txt Emails the address a one-time link and records a pending invitation for seven days. `POST /orgs/{org}/invites` ## TL;DR | Question | Answer | |---|---| | What does it do? | Invite an address to an organisation. | | What does it take? | A JSON body of type [`CreateInviteBody`](https://docs.plotwork.dev/api/schemas.md#createinvitebody). | | What does it return? | Status 201 with a body of type [`OrganisationInvite`](https://docs.plotwork.dev/api/schemas.md#organisationinvite). | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` the caller's role may not invite (orgs.forbidden), the organisation is full (orgs.member_limit_reached), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `409` the address is already a member (orgs.already_member) or already invited (orgs.already_invited); `429` too many requests; `502` the email could not be sent, and no invitation was kept; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Emails the address a one-time link and records a pending invitation for seven days. The person must sign in with that address to accept. The owner invites as admin, member or reader, an admin only as member or reader. 403 orgs.member_limit_reached when members plus pending invitations fill the owner's plan; 502 and no invitation when the email cannot be sent. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Request body The body has the type [`CreateInviteBody`](https://docs.plotwork.dev/api/schemas.md#createinvitebody). | Field | Type | Required | Description | |---|---|---|---| | `email` | `string` | yes | The address to invite. The person must sign in with this address to accept. at least 3 characters; at most 254 characters. | | `role` | `"admin" \| "member" \| "reader"` | yes | `admin`, `member` or `reader`. An admin can invite and remove members and readers, rename the organisation and manage its keys; a member can publish and change their own diagrams; a reader can view and download and change nothing. Only the owner offers `admin` or changes a role. | ## Responses | Status | Meaning | Body | |---|---|---| | 201 | The invitation. | [`OrganisationInvite`](https://docs.plotwork.dev/api/schemas.md#organisationinvite) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The caller's role may not invite (orgs.forbidden), the organisation is full (orgs.member_limit_reached), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | The address is already a member (orgs.already_member) or already invited (orgs.already_invited). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 502 | The email could not be sent, and no invitation was kept. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 201 answer has the type [`OrganisationInvite`](https://docs.plotwork.dev/api/schemas.md#organisationinvite). | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `email` | `string` | yes | | | `role` | `"admin" \| "member" \| "reader"` | yes | | | `createdAt` | `integer` | yes | Milliseconds since the epoch. | | `expiresAt` | `integer` | yes | Milliseconds since the epoch. Seven days after it was made. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Revoke an invitation > Page: https://docs.plotwork.dev/api/delete-orgs-org-invites-id. Index of every page: https://docs.plotwork.dev/llms.txt Deletes a pending invitation, so its link stops working. `DELETE /orgs/{org}/invites/{id}` ## TL;DR | Question | Answer | |---|---| | What does it do? | Revoke an invitation. | | What does it take? | `org`, `id` in the path. | | What does it return? | Status 204 with no body. | | What can go wrong? | `401` not signed in; `403` the caller's role may not revoke this invitation (orgs.forbidden), or the request came from an origin that may not make changes; `404` no such organisation or invitation, or the caller is not a member of the organisation; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Deletes a pending invitation, so its link stops working. The owner may revoke any; an admin only an invitation to member or reader. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | | `id` | `string` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Revoked. | none | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The caller's role may not revoke this invitation (orgs.forbidden), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation or invitation, or the caller is not a member of the organisation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Accept an invitation > Page: https://docs.plotwork.dev/api/post-orgs-invites-accept. Index of every page: https://docs.plotwork.dev/llms.txt Joins the organisation the token was made for. `POST /orgs/invites/accept` ## TL;DR | Question | Answer | |---|---| | What does it do? | Accept an invitation. | | What does it take? | A JSON body of type [`AcceptInviteBody`](https://docs.plotwork.dev/api/schemas.md#acceptinvitebody). | | What does it return? | Status 200 with a body of type [`Organisation`](https://docs.plotwork.dev/api/schemas.md#organisation). | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` the invitation was sent to another address (orgs.invite_wrong_account), the organisation is full (orgs.member_limit_reached), or the request came from an origin that may not make changes; `404` the token is unknown, used, or expired (orgs.invite_invalid); `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Joins the organisation the token was made for. The signed-in account's address must equal the invited one (403 orgs.invite_wrong_account, which names the invited address). An unknown, used, or expired token is 404 orgs.invite_invalid, with one message for all three. 403 orgs.member_limit_reached if the organisation has filled since. ## Request body The body has the type [`AcceptInviteBody`](https://docs.plotwork.dev/api/schemas.md#acceptinvitebody). | Field | Type | Required | Description | |---|---|---|---| | `token` | `string` | yes | The token in the invitation link. at least 1 character; at most 200 characters. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The organisation, as its new member sees it. | [`Organisation`](https://docs.plotwork.dev/api/schemas.md#organisation) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The invitation was sent to another address (orgs.invite_wrong_account), the organisation is full (orgs.member_limit_reached), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | The token is unknown, used, or expired (orgs.invite_invalid). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`Organisation`](https://docs.plotwork.dev/api/schemas.md#organisation). | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `slug` | `string` | yes | | | `name` | `string` | yes | | | `role` | `"owner" \| "admin" \| "member" \| "reader"` | yes | What the caller is in this organisation. | | `createdAt` | `integer` | yes | Milliseconds since the epoch. | | `memberCount` | `integer` | yes | People who have joined, the owner included. The same number as `usage.members`. minimum 0. | | `plan` | `"free" \| "pro" \| "enterprise"` | yes | The organisation's plan: `enterprise` under a contract, otherwise its owner's. | | `contractEndsOn` | `string \| null` | yes | The last day of the Enterprise contract, `YYYY-MM-DD`, or null when there is none or it has no end date written. The terms of a contract are not shown to members. | | `usage` | `OrganisationUsage` | yes | | | `usage.diagrams` | `integer` | yes | minimum 0. | | `usage.bytes` | `integer` | yes | minimum 0. | | `usage.members` | `integer` | yes | People who have joined, the owner included. minimum 0. | | `usage.invites` | `integer` | yes | Invitations that can still be accepted. They count toward the member limit. minimum 0. | | `limits` | `OrganisationLimits` | yes | | | `limits.dailyCalls` | `integer` | yes | | | `limits.hostedDiagrams` | `integer` | yes | Diagrams the organisation may hold. | | `limits.hostedBytes` | `integer` | yes | Total bytes of diagrams the organisation may hold. | | `limits.retentionDays` | `integer \| null` | yes | Days a diagram is kept after it was last published, or null to keep it until it is deleted. | | `limits.maxDiagramBytes` | `integer` | yes | | | `limits.stableLinks` | `integer` | yes | Stable links the owner may keep for their own diagrams. An organisation's diagrams have none. | | `limits.organisations` | `integer` | yes | Organisations an account on this plan may own. | | `limits.orgMembers` | `integer` | yes | People one organisation may have, the owner included and pending invitations counted. | | `limits.orgIcons` | `integer` | yes | Custom icons one organisation may keep; zero on a plan without custom icons. minimum 0. | | `limits.driftHistoryDays` | `integer` | yes | Days the drift dashboard keeps a run and a status after it was reported; zero on a plan without the dashboard. minimum 0. | | `limits.auditRetentionDays` | `integer` | yes | Days the organisation's audit log keeps an event; zero on a plan that records none. minimum 0. | | `avatar` | `OrgAvatar` | yes | The organisation's uploaded avatar, or null when it has none and a mark made from its id stands in. | | `avatar.version` | `string` | yes | Changes with every upload. Put it in the address (?v=) so a new picture is a new address. | | `avatar.contentType` | `"image/svg+xml" \| "image/png" \| "image/gif" \| "image/webp"` | yes | | | `avatar.animated` | `boolean` | yes | True when it moves, so a page can hold it still for a person who asked for less motion. | | `keepEditable` | `boolean` | yes | The owner's setting "Keep diagrams editable": true when a publish to the organisation that does not send embedSource writes the diagram source into the picture, so members can edit it in the portal later. Every member can open such a picture and read its source. | | `publishing` | `OrganisationPublishing` | yes | | | `publishing.policy` | `"personal" \| "organisation-default" \| "organisation-required"` | yes | What the owner or an admin chose: personal (members publish where they choose), organisation-default (a member's publish that names no organisation goes to this library), or organisation-required (nothing a member publishes reaches a public link). | | `publishing.effective` | `"personal" \| "organisation-default" \| "organisation-required"` | yes | What is enforced now: the choice, except organisation-required on a plan that no longer includes it, which acts as organisation-default. | | `publishing.hideViewLinks` | `boolean` | yes | Whether hosted answers leave view links out for members and the organisation's keys. Honoured only while effective is organisation-required. | | `publishing.requiredAvailable` | `boolean` | yes | Whether the organisation's plan includes organisation-required (Pro and Enterprise). | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # List an organisation's diagrams > Page: https://docs.plotwork.dev/api/get-orgs-org-diagrams. Index of every page: https://docs.plotwork.dev/llms.txt The organisation's diagrams, most recently published first and at most 200, with where the organisation stands against its owner's plan. `GET /orgs/{org}/diagrams` ## TL;DR | Question | Answer | |---|---| | What does it do? | List an organisation's diagrams. | | What does it take? | `org` in the path. | | What does it return? | Status 200 with a body of type [`OrganisationDiagramList`](https://docs.plotwork.dev/api/schemas.md#organisationdiagramlist). | | What can go wrong? | `401` not signed in, or the key is not valid; `404` no such organisation, the caller is not a member of it, or the key belongs to another organisation; `429` too many requests, or the daily limit is used up; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does The organisation's diagrams, most recently published first and at most 200, with where the organisation stands against its owner's plan. A signed-in member, an organisation's key, or a member's own key may call it. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The diagrams and the usage. | [`OrganisationDiagramList`](https://docs.plotwork.dev/api/schemas.md#organisationdiagramlist) | | 401 | Not signed in, or the key is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, the caller is not a member of it, or the key belongs to another organisation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests, or the daily limit is used up. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`OrganisationDiagramList`](https://docs.plotwork.dev/api/schemas.md#organisationdiagramlist). | Field | Type | Required | Description | |---|---|---|---| | `items` | `OrganisationDiagram[]` | yes | At most 200, newest published first. | | `items[].id` | `string` | yes | The handle for the diagram in the routes below. | | `items[].name` | `string \| null` | yes | The label given at publish, or null. | | `items[].folder` | `string \| null` | yes | The folder it is filed in, such as `Payments/Checkout`, or null for the top level. | | `items[].theme` | `"light" \| "dark" \| "auto"` | yes | The color scheme it was drawn in: light, dark, or auto. | | `items[].bytes` | `integer` | yes | Size of the stored SVG in bytes. | | `items[].createdBy` | `string \| null` | yes | The account that published it, or null when that account has left or an organisation key published it. | | `items[].createdAt` | `integer` | yes | When it was first published, in milliseconds since the epoch. | | `items[].publishedAt` | `integer` | yes | When it was last published, in milliseconds since the epoch. Publishing the same diagram again moves it. | | `items[].expiresAt` | `integer \| null` | yes | When it will be deleted, in milliseconds since the epoch, or null when the owner's plan keeps diagrams until they are deleted. | | `items[].viewUrl` | `string` | yes | A page in the portal that shows the diagram to a signed-in member. It is not a link anyone can open: an organisation's diagrams have no public address. | | `items[].locked` | `boolean` | yes | True while the owner has the diagram locked: nobody can delete, rename or move it, its publisher and the owner included, until the owner unlocks it. A lock does not stop it expiring on a plan that expires diagrams. | | `items[].lockedAt` | `integer \| null` | yes | When the owner locked it, in milliseconds since the epoch, or null when it is not locked. | | `items[].restricted` | `true` | no | Present, and true, when the owner has restricted this diagram's folder and the caller is allowed in it, so the portal can show it as locked. Absent otherwise. A diagram in a folder the caller may not use is not listed at all. | | `usage` | `OrganisationHostingUsage` | yes | | | `usage.plan` | `"free" \| "pro" \| "enterprise"` | yes | | | `usage.count` | `integer` | yes | Diagrams the organisation holds. This is the total, even when the list is shorter. minimum 0. | | `usage.bytes` | `integer` | yes | minimum 0. | | `usage.limits` | `OrganisationLimits` | yes | | | `usage.limits.dailyCalls` | `integer` | yes | | | `usage.limits.hostedDiagrams` | `integer` | yes | Diagrams the organisation may hold. | | `usage.limits.hostedBytes` | `integer` | yes | Total bytes of diagrams the organisation may hold. | | `usage.limits.retentionDays` | `integer \| null` | yes | Days a diagram is kept after it was last published, or null to keep it until it is deleted. | | `usage.limits.maxDiagramBytes` | `integer` | yes | | | `usage.limits.stableLinks` | `integer` | yes | Stable links the owner may keep for their own diagrams. An organisation's diagrams have none. | | `usage.limits.organisations` | `integer` | yes | Organisations an account on this plan may own. | | `usage.limits.orgMembers` | `integer` | yes | People one organisation may have, the owner included and pending invitations counted. | | `usage.limits.orgIcons` | `integer` | yes | Custom icons one organisation may keep; zero on a plan without custom icons. minimum 0. | | `usage.limits.driftHistoryDays` | `integer` | yes | Days the drift dashboard keeps a run and a status after it was reported; zero on a plan without the dashboard. minimum 0. | | `usage.limits.auditRetentionDays` | `integer` | yes | Days the organisation's audit log keeps an event; zero on a plan that records none. minimum 0. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Publish a diagram to an organisation > Page: https://docs.plotwork.dev/api/post-orgs-org-diagrams. Index of every page: https://docs.plotwork.dev/llms.txt Renders the source and stores the SVG in the organisation's private library; there is no public link, only a viewUrl for signed-in members. `POST /orgs/{org}/diagrams` ## TL;DR | Question | Answer | |---|---| | What does it do? | Publish a diagram to an organisation. | | What does it take? | A JSON body of type [`PublishOrganisationDiagramBody`](https://docs.plotwork.dev/api/schemas.md#publishorganisationdiagrambody). | | What does it return? | Status 200 with a body of type [`PublishedOrganisationDiagram`](https://docs.plotwork.dev/api/schemas.md#publishedorganisationdiagram). | | What can go wrong? | `400` the body failed schema validation; `401` not signed in, or the key is not valid; `403` the caller's role may not publish (orgs.forbidden), the folder is restricted to others (orgs.folder_restricted), the organisation's plan has no room for another diagram (hosting.quota_exceeded), or the request came from an origin that may not make changes; `404` no such organisation, the caller is not a member of it, or the key belongs to another organisation; `409` the diagram is already in the library and locked, and the publish names it or files it elsewhere (hosting.diagram_locked). only the owner can unlock it; `413` the body is larger than the size cap, or the diagram renders larger than the plan hosts; `422` the diagram source has errors; the diagnostics are in details; `429` too many requests, or the daily limit is used up; `502` the diagram could not be stored; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Renders the source and stores the SVG in the organisation's private library; there is no public link, only a viewUrl for signed-in members. The same diagram published again keeps its place (200, created false) and moves its expiry out. 403 hosting.quota_exceeded carries the organisation's numbers; 413 hosting.too_large says the diagram is larger than the plan hosts. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Request body The body has the type [`PublishOrganisationDiagramBody`](https://docs.plotwork.dev/api/schemas.md#publishorganisationdiagrambody). | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | Diagram source; the first line is the header, such as flow "Title" (draw_diagram lists every kind). at least 1 character; at most 50000 characters. | | `settings` | `object` | no | The repo's plotwork.config.json, if any. Schema: https://docs.plotwork.dev/schemas/plotwork.config.json. | | `theme` | `"light" \| "dark" \| "auto"` | no | The color scheme to draw it in: light, dark, or auto (one picture that follows the viewer's light or dark setting). default "light". | | `embedSource` | `boolean` | no | Write the diagram source into the stored SVG, in its metadata, so the diagram can be edited later; every member who can open it can then read the source. The same diagram published with and without it is two pictures. Left out, the organisation's keepEditable setting decides (false unless the owner turned it on); false always leaves the source out. | | `name` | `string` | no | A label for the diagram in your list and in the Markdown snippet. Optional. at least 1 character; at most 80 characters; pattern `^[^\p{Cc}]+$/u`. | | `folder` | `string \| null` | no | The folder to file the diagram in. Left out, a new diagram goes to the top level and a diagram that is already hosted stays where it is. A path files a new diagram there, or moves the one that is already hosted and keeps its link. null puts it at the top level. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The diagram was already in the library; it was kept. | [`PublishedOrganisationDiagram`](https://docs.plotwork.dev/api/schemas.md#publishedorganisationdiagram) | | 201 | The diagram was stored. | [`PublishedOrganisationDiagram`](https://docs.plotwork.dev/api/schemas.md#publishedorganisationdiagram) | | 400 | The body failed schema validation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in, or the key is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The caller's role may not publish (orgs.forbidden), the folder is restricted to others (orgs.folder_restricted), the organisation's plan has no room for another diagram (hosting.quota_exceeded), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, the caller is not a member of it, or the key belongs to another organisation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | The diagram is already in the library and locked, and the publish names it or files it elsewhere (hosting.diagram_locked). Only the owner can unlock it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than the size cap, or the diagram renders larger than the plan hosts. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 422 | The diagram source has errors; the diagnostics are in details. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests, or the daily limit is used up. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 502 | The diagram could not be stored. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`PublishedOrganisationDiagram`](https://docs.plotwork.dev/api/schemas.md#publishedorganisationdiagram). | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The handle for the diagram in the routes below. | | `name` | `string \| null` | yes | The label given at publish, or null. | | `folder` | `string \| null` | yes | The folder it is filed in, such as `Payments/Checkout`, or null for the top level. | | `theme` | `"light" \| "dark" \| "auto"` | yes | The color scheme it was drawn in: light, dark, or auto. | | `bytes` | `integer` | yes | Size of the stored SVG in bytes. | | `createdBy` | `string \| null` | yes | The account that published it, or null when that account has left or an organisation key published it. | | `createdAt` | `integer` | yes | When it was first published, in milliseconds since the epoch. | | `publishedAt` | `integer` | yes | When it was last published, in milliseconds since the epoch. Publishing the same diagram again moves it. | | `expiresAt` | `integer \| null` | yes | When it will be deleted, in milliseconds since the epoch, or null when the owner's plan keeps diagrams until they are deleted. | | `viewUrl` | `string` | yes | A page in the portal that shows the diagram to a signed-in member. It is not a link anyone can open: an organisation's diagrams have no public address. | | `locked` | `boolean` | yes | True while the owner has the diagram locked: nobody can delete, rename or move it, its publisher and the owner included, until the owner unlocks it. A lock does not stop it expiring on a plan that expires diagrams. | | `lockedAt` | `integer \| null` | yes | When the owner locked it, in milliseconds since the epoch, or null when it is not locked. | | `restricted` | `true` | no | Present, and true, when the owner has restricted this diagram's folder and the caller is allowed in it, so the portal can show it as locked. Absent otherwise. A diagram in a folder the caller may not use is not listed at all. | | `created` | `boolean` | yes | True when this call stored a new diagram; false when the same diagram was already in the library and was kept. | | `sourceEmbedded` | `boolean` | yes | True when the stored picture carries the diagram source: the call sent embedSource true, or left it out while the organisation's keepEditable is on. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Download an organisation diagram's SVG > Page: https://docs.plotwork.dev/api/get-orgs-org-diagrams-id-svg. Index of every page: https://docs.plotwork.dev/llms.txt Streams the stored picture to a signed-in member, an organisation's key, or a member's own key, with headers that keep it out of shared caches and out of scripts. `GET /orgs/{org}/diagrams/{id}/svg` ## TL;DR | Question | Answer | |---|---| | What does it do? | Download an organisation diagram's SVG. | | What does it take? | `org`, `id` in the path, and `download` in the query string. | | What does it return? | Status 200 with no body. | | What can go wrong? | `401` not signed in, or the key is not valid; `404` no such organisation or diagram, the caller is not a member, or the key belongs to another organisation; `429` too many requests, or the daily limit is used up; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Streams the stored picture to a signed-in member, an organisation's key, or a member's own key, with headers that keep it out of shared caches and out of scripts. With ?download=1 it is an attachment named after the diagram. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | | `id` | `string` | yes | | ## Query parameters | Field | Type | Required | Description | |---|---|---|---| | `download` | `"0" \| "1"` | no | `1` sends the file as an attachment, named after the diagram. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The SVG. | none | | 401 | Not signed in, or the key is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation or diagram, the caller is not a member, or the key belongs to another organisation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests, or the daily limit is used up. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # File or rename an organisation diagram > Page: https://docs.plotwork.dev/api/patch-orgs-org-diagrams-id. Index of every page: https://docs.plotwork.dev/llms.txt Moves the diagram to a folder (or the top level with null) and/or renames it. `PATCH /orgs/{org}/diagrams/{id}` ## TL;DR | Question | Answer | |---|---| | What does it do? | File or rename an organisation diagram. | | What does it take? | A JSON body of type [`UpdateOrganisationDiagramBody`](https://docs.plotwork.dev/api/schemas.md#updateorganisationdiagrambody). | | What does it return? | Status 204 with no body. | | What can go wrong? | `400` the body is not valid, or the folder path breaks the rules; details says why; `401` not signed in, or the key is not valid; `403` a member may change only a diagram they published and a reader none (orgs.forbidden), the folder it is moved to is restricted to others (orgs.folder_restricted), or the request came from an origin that may not make changes; `404` no such organisation or diagram, the diagram is in a folder the caller may not use, the caller is not a member, or the key belongs to another organisation; `409` the diagram is locked (hosting.diagram_locked); only the owner can unlock it. details says when it was locked and whether the caller may unlock it; `429` too many requests, or the daily limit is used up; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Moves the diagram to a folder (or the top level with null) and/or renames it. A member changes only a diagram they published; an admin, the owner, and the organisation's key change any. An invalid folder path is a 400 with the reason, never rewritten. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | | `id` | `string` | yes | | ## Request body The body has the type [`UpdateOrganisationDiagramBody`](https://docs.plotwork.dev/api/schemas.md#updateorganisationdiagrambody). | Field | Type | Required | Description | |---|---|---|---| | `folder` | `string \| null` | no | The folder to file the diagram in, or null for the top level. | | `name` | `string \| null` | no | A new label, or null to clear it. at least 1 character; at most 80 characters; pattern `^[^\p{Cc}]+$/u`. | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Changed. | none | | 400 | The body is not valid, or the folder path breaks the rules; details says why. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in, or the key is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A member may change only a diagram they published and a reader none (orgs.forbidden), the folder it is moved to is restricted to others (orgs.folder_restricted), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation or diagram, the diagram is in a folder the caller may not use, the caller is not a member, or the key belongs to another organisation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | The diagram is locked (hosting.diagram_locked); only the owner can unlock it. details says when it was locked and whether the caller may unlock it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests, or the daily limit is used up. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Delete an organisation diagram > Page: https://docs.plotwork.dev/api/delete-orgs-org-diagrams-id. Index of every page: https://docs.plotwork.dev/llms.txt Removes the diagram's record, then its file from the private bucket. `DELETE /orgs/{org}/diagrams/{id}` ## TL;DR | Question | Answer | |---|---| | What does it do? | Delete an organisation diagram. | | What does it take? | `org`, `id` in the path. | | What does it return? | Status 204 with no body. | | What can go wrong? | `401` not signed in, or the key is not valid; `403` a member may delete only a diagram they published and a reader none (orgs.forbidden), or the request came from an origin that may not make changes; `404` no such organisation or diagram, the diagram is in a folder the caller may not use, the caller is not a member, or the key belongs to another organisation; `409` the diagram is locked (hosting.diagram_locked); only the owner can unlock it. details says when it was locked and whether the caller may unlock it; `429` too many requests, or the daily limit is used up; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Removes the diagram's record, then its file from the private bucket. A member deletes only a diagram they published; an admin, the owner, and the organisation's key delete any. A locked diagram is a 409 for everyone until the owner unlocks it. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | | `id` | `string` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Deleted. | none | | 401 | Not signed in, or the key is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A member may delete only a diagram they published and a reader none (orgs.forbidden), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation or diagram, the diagram is in a folder the caller may not use, the caller is not a member, or the key belongs to another organisation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | The diagram is locked (hosting.diagram_locked); only the owner can unlock it. details says when it was locked and whether the caller may unlock it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests, or the daily limit is used up. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Lock an organisation diagram > Page: https://docs.plotwork.dev/api/put-orgs-org-diagrams-id-lock. Index of every page: https://docs.plotwork.dev/llms.txt Locks the diagram so nobody can delete, rename or move it, the member who published it and the owner included, until the owner unlocks it. `PUT /orgs/{org}/diagrams/{id}/lock` ## TL;DR | Question | Answer | |---|---| | What does it do? | Lock an organisation diagram. | | What does it take? | `org`, `id` in the path. | | What does it return? | Status 204 with no body. | | What can go wrong? | `401` not signed in; `403` only the owner may lock a diagram (orgs.forbidden), or the request came from an origin that may not make changes; `404` no such organisation or diagram, the diagram is in a folder the caller may not use, or the caller is not a member; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Locks the diagram so nobody can delete, rename or move it, the member who published it and the owner included, until the owner unlocks it. Only the owner may lock or unlock. A lock does not stop the plan's expiry. Session only. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | | `id` | `string` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Locked. | none | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | Only the owner may lock a diagram (orgs.forbidden), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation or diagram, the diagram is in a folder the caller may not use, or the caller is not a member. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Unlock an organisation diagram > Page: https://docs.plotwork.dev/api/delete-orgs-org-diagrams-id-lock. Index of every page: https://docs.plotwork.dev/llms.txt Removes the lock, so the diagram can be deleted, renamed and moved again by whoever may. `DELETE /orgs/{org}/diagrams/{id}/lock` ## TL;DR | Question | Answer | |---|---| | What does it do? | Unlock an organisation diagram. | | What does it take? | `org`, `id` in the path. | | What does it return? | Status 204 with no body. | | What can go wrong? | `401` not signed in; `403` only the owner may unlock a diagram (orgs.forbidden), or the request came from an origin that may not make changes; `404` no such organisation or diagram, the diagram is in a folder the caller may not use, or the caller is not a member; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Removes the lock, so the diagram can be deleted, renamed and moved again by whoever may. Only the owner of the organisation may unlock. Session only. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | | `id` | `string` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Unlocked. | none | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | Only the owner may unlock a diagram (orgs.forbidden), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation or diagram, the diagram is in a folder the caller may not use, or the caller is not a member. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Save edited source over an organisation diagram > Page: https://docs.plotwork.dev/api/put-orgs-org-diagrams-id-source. Index of every page: https://docs.plotwork.dev/llms.txt Draws the source with the organisation's brand and icons and stores it in place of the diagram's picture, keeping its id, name and folder; the replaced picture is kept as a previous version. `PUT /orgs/{org}/diagrams/{id}/source` ## TL;DR | Question | Answer | |---|---| | What does it do? | Save edited source over an organisation diagram. | | What does it take? | A JSON body of type [`SaveDiagramSourceBody`](https://docs.plotwork.dev/api/schemas.md#savediagramsourcebody). | | What does it return? | Status 200 with a body of type [`EditedOrganisationDiagram`](https://docs.plotwork.dev/api/schemas.md#editedorganisationdiagram). | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` the role may not change this diagram (orgs.forbidden), the owner's plan does not include the editor (plan.editor_not_included), the picture does not fit the plan (hosting.quota_exceeded), or the request came from an origin that may not make changes; `404` no such organisation or diagram, the diagram is in a folder the caller may not use, or the caller is not a member; `409` locked (hosting.diagram_locked), saved since (diagram.version_conflict, details.currentversion), or another diagram already has this picture (diagram.duplicate_content); `413` the body is larger than the size cap, or the diagram renders larger than the plan hosts; `422` the diagram source has errors; the diagnostics are in details; `429` too many requests; `502` the diagram could not be stored; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Draws the source with the organisation's brand and icons and stores it in place of the diagram's picture, keeping its id, name and folder; the replaced picture is kept as a previous version. baseVersion is the publishedAt the editor loaded: a diagram saved since is 409 diagram.version_conflict with details.currentVersion. The owner's plan must include the diagram editor. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | | `id` | `string` | yes | | ## Request body The body has the type [`SaveDiagramSourceBody`](https://docs.plotwork.dev/api/schemas.md#savediagramsourcebody). | Field | Type | Required | Description | |---|---|---|---| | `source` | `string` | yes | Diagram source; the first line is the header, such as flow "Title" (draw_diagram lists every kind). at least 1 character; at most 50000 characters. | | `theme` | `"light" \| "dark" \| "auto"` | no | The color scheme to draw it in: light, dark, or auto. Left out, the theme the diagram has. | | `baseVersion` | `integer` | yes | The diagram's publishedAt (milliseconds since the epoch) when the editor loaded it. If the diagram was saved since, the answer is 409 diagram.version_conflict with details.currentVersion. minimum 0. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The diagram as the list shows it, with warnings and changed. | [`EditedOrganisationDiagram`](https://docs.plotwork.dev/api/schemas.md#editedorganisationdiagram) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The role may not change this diagram (orgs.forbidden), the owner's plan does not include the editor (plan.editor_not_included), the picture does not fit the plan (hosting.quota_exceeded), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation or diagram, the diagram is in a folder the caller may not use, or the caller is not a member. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | Locked (hosting.diagram_locked), saved since (diagram.version_conflict, details.currentVersion), or another diagram already has this picture (diagram.duplicate_content). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than the size cap, or the diagram renders larger than the plan hosts. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 422 | The diagram source has errors; the diagnostics are in details. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 502 | The diagram could not be stored. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`EditedOrganisationDiagram`](https://docs.plotwork.dev/api/schemas.md#editedorganisationdiagram). | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The handle for the diagram in the routes below. | | `name` | `string \| null` | yes | The label given at publish, or null. | | `folder` | `string \| null` | yes | The folder it is filed in, such as `Payments/Checkout`, or null for the top level. | | `theme` | `"light" \| "dark" \| "auto"` | yes | The color scheme it was drawn in: light, dark, or auto. | | `bytes` | `integer` | yes | Size of the stored SVG in bytes. | | `createdBy` | `string \| null` | yes | The account that published it, or null when that account has left or an organisation key published it. | | `createdAt` | `integer` | yes | When it was first published, in milliseconds since the epoch. | | `publishedAt` | `integer` | yes | When it was last published, in milliseconds since the epoch. Publishing the same diagram again moves it. | | `expiresAt` | `integer \| null` | yes | When it will be deleted, in milliseconds since the epoch, or null when the owner's plan keeps diagrams until they are deleted. | | `viewUrl` | `string` | yes | A page in the portal that shows the diagram to a signed-in member. It is not a link anyone can open: an organisation's diagrams have no public address. | | `locked` | `boolean` | yes | True while the owner has the diagram locked: nobody can delete, rename or move it, its publisher and the owner included, until the owner unlocks it. A lock does not stop it expiring on a plan that expires diagrams. | | `lockedAt` | `integer \| null` | yes | When the owner locked it, in milliseconds since the epoch, or null when it is not locked. | | `restricted` | `true` | no | Present, and true, when the owner has restricted this diagram's folder and the caller is allowed in it, so the portal can show it as locked. Absent otherwise. A diagram in a folder the caller may not use is not listed at all. | | `warnings` | `Diagnostic[]` | yes | Quality warnings from drawing the source, as draw_diagram gives them. Empty for a restore. | | `warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `changed` | `boolean` | yes | False when the source draws the picture the diagram already has: nothing was stored and no version was kept. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # List an organisation diagram's previous versions > Page: https://docs.plotwork.dev/api/get-orgs-org-diagrams-id-versions. Index of every page: https://docs.plotwork.dev/llms.txt The pictures the diagram had before it was edited or restored in the portal, newest first, at most ten. `GET /orgs/{org}/diagrams/{id}/versions` ## TL;DR | Question | Answer | |---|---| | What does it do? | List an organisation diagram's previous versions. | | What does it take? | `org`, `id` in the path. | | What does it return? | Status 200 with a body of type [`DiagramVersionList`](https://docs.plotwork.dev/api/schemas.md#diagramversionlist). | | What can go wrong? | `401` not signed in; `404` no such organisation or diagram, the diagram is in a folder the caller may not use, or the caller is not a member; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does The pictures the diagram had before it was edited or restored in the portal, newest first, at most ten. Every role that can see the diagram may look. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | | `id` | `string` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The versions. | [`DiagramVersionList`](https://docs.plotwork.dev/api/schemas.md#diagramversionlist) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation or diagram, the diagram is in a folder the caller may not use, or the caller is not a member. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`DiagramVersionList`](https://docs.plotwork.dev/api/schemas.md#diagramversionlist). | Field | Type | Required | Description | |---|---|---|---| | `items` | `DiagramVersion[]` | yes | Newest first. A diagram keeps at most ten. | | `items[].id` | `string` | yes | The handle for restoring it. | | `items[].savedAt` | `integer` | yes | When it stopped being the current picture, in milliseconds since the epoch. | | `items[].bytes` | `integer` | yes | Size of the stored SVG in bytes. Versions do not count toward the plan. | | `items[].theme` | `"light" \| "dark" \| "auto"` | yes | | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Restore a previous version of an organisation diagram > Page: https://docs.plotwork.dev/api/post-orgs-org-diagrams-id-versions-version-id-restore. Index of every page: https://docs.plotwork.dev/llms.txt Makes the version the diagram's picture, with the checks and the answer of a save. `POST /orgs/{org}/diagrams/{id}/versions/{versionId}/restore` ## TL;DR | Question | Answer | |---|---| | What does it do? | Restore a previous version of an organisation diagram. | | What does it take? | A JSON body of type [`RestoreDiagramVersionBody`](https://docs.plotwork.dev/api/schemas.md#restorediagramversionbody). | | What does it return? | Status 200 with a body of type [`EditedOrganisationDiagram`](https://docs.plotwork.dev/api/schemas.md#editedorganisationdiagram). | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` the role may not change this diagram (orgs.forbidden), the owner's plan does not include the editor (plan.editor_not_included), the picture does not fit the plan (hosting.quota_exceeded), or the request came from an origin that may not make changes; `404` no such organisation, diagram or version, or the caller is not a member; `409` locked (hosting.diagram_locked), saved since (diagram.version_conflict), or another diagram already has this picture (diagram.duplicate_content); `429` too many requests; `502` the diagram could not be stored; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Makes the version the diagram's picture, with the checks and the answer of a save. The current picture becomes a version. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | | `id` | `string` | yes | | | `versionId` | `string` | yes | | ## Request body The body has the type [`RestoreDiagramVersionBody`](https://docs.plotwork.dev/api/schemas.md#restorediagramversionbody). | Field | Type | Required | Description | |---|---|---|---| | `baseVersion` | `integer` | yes | The diagram's publishedAt (milliseconds since the epoch) when the editor loaded it. If the diagram was saved since, the answer is 409 diagram.version_conflict with details.currentVersion. minimum 0. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The diagram as the list shows it. | [`EditedOrganisationDiagram`](https://docs.plotwork.dev/api/schemas.md#editedorganisationdiagram) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The role may not change this diagram (orgs.forbidden), the owner's plan does not include the editor (plan.editor_not_included), the picture does not fit the plan (hosting.quota_exceeded), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, diagram or version, or the caller is not a member. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | Locked (hosting.diagram_locked), saved since (diagram.version_conflict), or another diagram already has this picture (diagram.duplicate_content). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 502 | The diagram could not be stored. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`EditedOrganisationDiagram`](https://docs.plotwork.dev/api/schemas.md#editedorganisationdiagram). | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The handle for the diagram in the routes below. | | `name` | `string \| null` | yes | The label given at publish, or null. | | `folder` | `string \| null` | yes | The folder it is filed in, such as `Payments/Checkout`, or null for the top level. | | `theme` | `"light" \| "dark" \| "auto"` | yes | The color scheme it was drawn in: light, dark, or auto. | | `bytes` | `integer` | yes | Size of the stored SVG in bytes. | | `createdBy` | `string \| null` | yes | The account that published it, or null when that account has left or an organisation key published it. | | `createdAt` | `integer` | yes | When it was first published, in milliseconds since the epoch. | | `publishedAt` | `integer` | yes | When it was last published, in milliseconds since the epoch. Publishing the same diagram again moves it. | | `expiresAt` | `integer \| null` | yes | When it will be deleted, in milliseconds since the epoch, or null when the owner's plan keeps diagrams until they are deleted. | | `viewUrl` | `string` | yes | A page in the portal that shows the diagram to a signed-in member. It is not a link anyone can open: an organisation's diagrams have no public address. | | `locked` | `boolean` | yes | True while the owner has the diagram locked: nobody can delete, rename or move it, its publisher and the owner included, until the owner unlocks it. A lock does not stop it expiring on a plan that expires diagrams. | | `lockedAt` | `integer \| null` | yes | When the owner locked it, in milliseconds since the epoch, or null when it is not locked. | | `restricted` | `true` | no | Present, and true, when the owner has restricted this diagram's folder and the caller is allowed in it, so the portal can show it as locked. Absent otherwise. A diagram in a folder the caller may not use is not listed at all. | | `warnings` | `Diagnostic[]` | yes | Quality warnings from drawing the source, as draw_diagram gives them. Empty for a restore. | | `warnings[].code` | `"E100" \| "E101" \| "E110" \| "E111" \| "E120" \| "E121" \| "E122" \| "E123" \| "E124" \| "E125" \| "E130" \| "E131" \| "E132" \| "E133" \| "E134" \| "E135" \| "E136" \| "E137" \| "E138" \| "E139" \| "E140" \| "W200" \| "W201" \| "W202" \| "W203" \| "W204" \| "W205" \| "W206" \| "W207" \| "W500" \| "M100" \| "M101" \| "M102" \| "M103" \| "M104" \| "M110" \| "M200" \| "M201" \| "M202" \| "M203" \| "M204" \| "M205" \| "M206" \| "M207" \| "M208" \| "M209" \| "M210" \| "M211" \| "M212" \| "M213" \| "M214" \| "M215" \| "M216" \| "M217" \| "M218" \| "E300" \| "E301" \| "E302" \| "E303" \| "E304" \| "W300" \| "W301" \| "W302" \| "W303" \| "M120" \| "M220" \| "M221" \| "M222" \| "E340" \| "E341" \| "E342" \| "E343" \| "E344" \| "E345" \| "W320" \| "W321" \| "W322" \| "W323" \| "M240" \| "M241" \| "M242" \| "E360" \| "E361" \| "E362" \| "E363" \| "E364" \| "E365" \| "E366" \| "E367" \| "W330" \| "W331" \| "W332" \| "W333" \| "W334" \| "M250" \| "M251" \| "E400" \| "E401" \| "E402" \| "E403" \| "W350" \| "W351" \| "W352" \| "W353" \| "M270" \| "M271" \| "E420" \| "E421" \| "E422" \| "E423" \| "E424" \| "W360" \| "W361" \| "W362" \| "W363" \| "W364" \| "M180" \| "M280" \| "M281" \| "M282" \| "E320" \| "E321" \| "E322" \| "E323" \| "E324" \| "E325" \| "E326" \| "E327" \| "E328" \| "E329" \| "E330" \| "W310" \| "W311" \| "W312" \| "W313" \| "W314" \| "M130" \| "M131" \| "M132" \| "M230" \| "M231" \| "M232" \| "M233" \| "M234" \| "M235" \| "M236" \| "M237" \| "M238" \| "E380" \| "E381" \| "E382" \| "E383" \| "W340" \| "W341" \| "W342" \| "W343" \| "M260" \| "I100" \| "I101" \| "I102" \| "I103" \| "I104" \| "I105" \| "I200" \| "I201" \| "I202" \| "I203" \| "I204" \| "I206" \| "R100" \| "R101" \| "R200" \| "R201" \| "R202" \| "R203" \| "R204" \| "R205" \| "R206" \| "R207" \| "S100" \| "S200" \| "S101" \| "S102" \| "S103" \| "S104" \| "S201" \| "S202" \| "S203"` | yes | Stable code to branch on; E-codes are errors, W-codes are warnings. | | `warnings[].severity` | `"error" \| "warning"` | yes | Errors stop rendering; warnings never do. | | `warnings[].message` | `string` | yes | What is wrong, in one sentence. | | `warnings[].line` | `integer` | yes | 1-based line in the submitted source. minimum 1. | | `warnings[].column` | `integer` | yes | 1-based column in that line. minimum 1. | | `warnings[].length` | `integer` | yes | Number of characters the problem covers. minimum 1. | | `warnings[].hint` | `string \| null` | yes | A concrete suggestion for fixing it, or null. | | `changed` | `boolean` | yes | False when the source draws the picture the diagram already has: nothing was stored and no version was kept. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Keep an organisation's diagrams editable > Page: https://docs.plotwork.dev/api/put-orgs-org-editable. Index of every page: https://docs.plotwork.dev/llms.txt Turns the owner's setting on or off. `PUT /orgs/{org}/editable` ## TL;DR | Question | Answer | |---|---| | What does it do? | Keep an organisation's diagrams editable. | | What does it take? | A JSON body of type [`KeepEditable`](https://docs.plotwork.dev/api/schemas.md#keepeditable). | | What does it return? | Status 200 with a body of type [`KeepEditable`](https://docs.plotwork.dev/api/schemas.md#keepeditable). | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` only the owner may change it (orgs.forbidden), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Turns the owner's setting on or off. On, a publish to the organisation that does not send embedSource writes the diagram source into the picture so members can edit it later; embedSource false still leaves it out. Only the owner may. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Request body The body has the type [`KeepEditable`](https://docs.plotwork.dev/api/schemas.md#keepeditable). | Field | Type | Required | Description | |---|---|---|---| | `keepEditable` | `boolean` | yes | True to write the diagram source into every picture published to the organisation that does not say otherwise, so members can edit it in the portal later. A publish that sends embedSource false still leaves it out. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The value now. | [`KeepEditable`](https://docs.plotwork.dev/api/schemas.md#keepeditable) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | Only the owner may change it (orgs.forbidden), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`KeepEditable`](https://docs.plotwork.dev/api/schemas.md#keepeditable). | Field | Type | Required | Description | |---|---|---|---| | `keepEditable` | `boolean` | yes | True to write the diagram source into every picture published to the organisation that does not say otherwise, so members can edit it in the portal later. A publish that sends embedSource false still leaves it out. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Choose where an organisation's members publish > Page: https://docs.plotwork.dev/api/put-orgs-org-publishing. Index of every page: https://docs.plotwork.dev/llms.txt personal, organisation-default (a member's publish that names no organisation goes to the private library) or organisation-required (nothing a member publishes reaches a public link). `PUT /orgs/{org}/publishing` ## TL;DR | Question | Answer | |---|---| | What does it do? | Choose where an organisation's members publish. | | What does it take? | A JSON body of type [`SetOrganisationPublishingBody`](https://docs.plotwork.dev/api/schemas.md#setorganisationpublishingbody). | | What does it return? | Status 200 with a body of type [`OrganisationPublishing`](https://docs.plotwork.dev/api/schemas.md#organisationpublishing). | | What can go wrong? | `400` the body is not valid, or hideviewlinks was sent with a policy other than organisation-required; `401` not signed in; `403` the role may not (orgs.forbidden), the plan does not include organisation-required (plan.publishing_not_included), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does personal, organisation-default (a member's publish that names no organisation goes to the private library) or organisation-required (nothing a member publishes reaches a public link). hideViewLinks, with organisation-required only, leaves view links out of hosted answers. The owner or an admin; organisation-required needs Pro or Enterprise. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Request body The body has the type [`SetOrganisationPublishingBody`](https://docs.plotwork.dev/api/schemas.md#setorganisationpublishingbody). | Field | Type | Required | Description | |---|---|---|---| | `policy` | `"personal" \| "organisation-default" \| "organisation-required"` | yes | | | `hideViewLinks` | `boolean` | no | Leave view links out of hosted answers for members and keys. Only with organisation-required. default false. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The setting now. | [`OrganisationPublishing`](https://docs.plotwork.dev/api/schemas.md#organisationpublishing) | | 400 | The body is not valid, or hideViewLinks was sent with a policy other than organisation-required. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The role may not (orgs.forbidden), the plan does not include organisation-required (plan.publishing_not_included), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`OrganisationPublishing`](https://docs.plotwork.dev/api/schemas.md#organisationpublishing). | Field | Type | Required | Description | |---|---|---|---| | `policy` | `"personal" \| "organisation-default" \| "organisation-required"` | yes | What the owner or an admin chose: personal (members publish where they choose), organisation-default (a member's publish that names no organisation goes to this library), or organisation-required (nothing a member publishes reaches a public link). | | `effective` | `"personal" \| "organisation-default" \| "organisation-required"` | yes | What is enforced now: the choice, except organisation-required on a plan that no longer includes it, which acts as organisation-default. | | `hideViewLinks` | `boolean` | yes | Whether hosted answers leave view links out for members and the organisation's keys. Honoured only while effective is organisation-required. | | `requiredAvailable` | `boolean` | yes | Whether the organisation's plan includes organisation-required (Pro and Enterprise). | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # List members' public diagrams for review > Page: https://docs.plotwork.dev/api/get-orgs-org-publishing-public-diagrams. Index of every page: https://docs.plotwork.dev/llms.txt While the organisation requires its library: the public diagrams members published since they joined, newest first, at most 200. `GET /orgs/{org}/publishing/public-diagrams` ## TL;DR | Question | Answer | |---|---| | What does it do? | List members' public diagrams for review. | | What does it take? | `org` in the path. | | What does it return? | Status 200 with a body of type [`MemberPublicDiagramList`](https://docs.plotwork.dev/api/schemas.md#memberpublicdiagramlist). | | What can go wrong? | `401` not signed in; `403` only the owner or an admin may (orgs.forbidden); `404` no such organisation, or the caller is not a member of it; `409` the organisation does not require its library now (orgs.publish_review_unavailable); `429` too many requests; `503` organisations or hosting are not set up on this server. Every failure uses the one error envelope. | ## What it does While the organisation requires its library: the public diagrams members published since they joined, newest first, at most 200. The owner or an admin. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The list. | [`MemberPublicDiagramList`](https://docs.plotwork.dev/api/schemas.md#memberpublicdiagramlist) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | Only the owner or an admin may (orgs.forbidden). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | The organisation does not require its library now (orgs.publish_review_unavailable). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations or hosting are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`MemberPublicDiagramList`](https://docs.plotwork.dev/api/schemas.md#memberpublicdiagramlist). | Field | Type | Required | Description | |---|---|---|---| | `items` | `MemberPublicDiagram[]` | yes | At most 200, newest published first. | | `items[].id` | `string` | yes | The personal diagram's id. | | `items[].name` | `string \| null` | yes | | | `items[].url` | `string` | yes | Its public link, which anyone who has it can open. | | `items[].createdAt` | `integer` | yes | | | `items[].publishedAt` | `integer` | yes | | | `items[].bytes` | `integer` | yes | | | `items[].locked` | `boolean` | yes | Whether the member locked it. Moving and unpublishing override the member's lock. | | `items[].stableLinks` | `integer` | yes | How many of the member's stable links show it now. minimum 0. | | `items[].member` | `object` | yes | | | `items[].member.userId` | `string` | yes | | | `items[].member.email` | `string` | yes | | | `total` | `integer` | yes | How many there are, which may be more than the list holds. minimum 0. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Move a member's public diagram into the library > Page: https://docs.plotwork.dev/api/post-orgs-org-publishing-public-diagrams-id-move. Index of every page: https://docs.plotwork.dev/llms.txt Copies the picture into the private library under a new id, then removes the public diagram and its stable links. `POST /orgs/{org}/publishing/public-diagrams/{id}/move` ## TL;DR | Question | Answer | |---|---| | What does it do? | Move a member's public diagram into the library. | | What does it take? | `org`, `id` in the path. | | What does it return? | Status 200 with a body of type [`MovedMemberDiagram`](https://docs.plotwork.dev/api/schemas.md#movedmemberdiagram). | | What can go wrong? | `401` not signed in; `403` only the owner or an admin may (orgs.forbidden), the library is full (hosting.quota_exceeded), or the request came from an origin that may not make changes; `404` no such organisation, the caller is not a member of it, or the diagram is not on the list; `409` the organisation does not require its library now (orgs.publish_review_unavailable); `413` the picture is larger than the plan hosts; `429` too many requests; `502` the picture could not be read or stored; nothing was removed; `503` organisations or hosting are not set up on this server. Every failure uses the one error envelope. | ## What it does Copies the picture into the private library under a new id, then removes the public diagram and its stable links. The owner or an admin, while the library is required. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | | `id` | `string` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | Moved. | [`MovedMemberDiagram`](https://docs.plotwork.dev/api/schemas.md#movedmemberdiagram) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | Only the owner or an admin may (orgs.forbidden), the library is full (hosting.quota_exceeded), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, the caller is not a member of it, or the diagram is not on the list. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | The organisation does not require its library now (orgs.publish_review_unavailable). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The picture is larger than the plan hosts. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 502 | The picture could not be read or stored; nothing was removed. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations or hosting are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`MovedMemberDiagram`](https://docs.plotwork.dev/api/schemas.md#movedmemberdiagram). | Field | Type | Required | Description | |---|---|---|---| | `diagram` | `OrganisationDiagram` | yes | | | `diagram.id` | `string` | yes | The handle for the diagram in the routes below. | | `diagram.name` | `string \| null` | yes | The label given at publish, or null. | | `diagram.folder` | `string \| null` | yes | The folder it is filed in, such as `Payments/Checkout`, or null for the top level. | | `diagram.theme` | `"light" \| "dark" \| "auto"` | yes | The color scheme it was drawn in: light, dark, or auto. | | `diagram.bytes` | `integer` | yes | Size of the stored SVG in bytes. | | `diagram.createdBy` | `string \| null` | yes | The account that published it, or null when that account has left or an organisation key published it. | | `diagram.createdAt` | `integer` | yes | When it was first published, in milliseconds since the epoch. | | `diagram.publishedAt` | `integer` | yes | When it was last published, in milliseconds since the epoch. Publishing the same diagram again moves it. | | `diagram.expiresAt` | `integer \| null` | yes | When it will be deleted, in milliseconds since the epoch, or null when the owner's plan keeps diagrams until they are deleted. | | `diagram.viewUrl` | `string` | yes | A page in the portal that shows the diagram to a signed-in member. It is not a link anyone can open: an organisation's diagrams have no public address. | | `diagram.locked` | `boolean` | yes | True while the owner has the diagram locked: nobody can delete, rename or move it, its publisher and the owner included, until the owner unlocks it. A lock does not stop it expiring on a plan that expires diagrams. | | `diagram.lockedAt` | `integer \| null` | yes | When the owner locked it, in milliseconds since the epoch, or null when it is not locked. | | `diagram.restricted` | `true` | no | Present, and true, when the owner has restricted this diagram's folder and the caller is allowed in it, so the portal can show it as locked. Absent otherwise. A diagram in a folder the caller may not use is not listed at all. | | `merged` | `boolean` | yes | True when the library already held exactly this picture, so only the public copy went. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Unpublish a member's public diagram > Page: https://docs.plotwork.dev/api/delete-orgs-org-publishing-public-diagrams-id. Index of every page: https://docs.plotwork.dev/llms.txt Removes the public diagram, its previous versions and its stable links. `DELETE /orgs/{org}/publishing/public-diagrams/{id}` ## TL;DR | Question | Answer | |---|---| | What does it do? | Unpublish a member's public diagram. | | What does it take? | `org`, `id` in the path. | | What does it return? | Status 204 with no body. | | What can go wrong? | `401` not signed in; `403` only the owner or an admin may (orgs.forbidden), or the request came from an origin that may not make changes; `404` no such organisation, the caller is not a member of it, or the diagram is not on the list; `409` the organisation does not require its library now (orgs.publish_review_unavailable); `429` too many requests; `503` organisations or hosting are not set up on this server. Every failure uses the one error envelope. | ## What it does Removes the public diagram, its previous versions and its stable links. It cannot be undone. The owner or an admin, while the library is required. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | | `id` | `string` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Unpublished. | none | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | Only the owner or an admin may (orgs.forbidden), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, the caller is not a member of it, or the diagram is not on the list. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | The organisation does not require its library now (orgs.publish_review_unavailable). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations or hosting are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Read where your publishes go > Page: https://docs.plotwork.dev/api/get-user-publishing. Index of every page: https://docs.plotwork.dev/llms.txt The organisations that claim the account's publishes, its default organisation, and where a publish that names no organisation goes now. `GET /user/publishing` ## TL;DR | Question | Answer | |---|---| | What does it do? | Read where your publishes go. | | What does it take? | Nothing. | | What does it return? | Status 200 with a body of type [`UserPublishing`](https://docs.plotwork.dev/api/schemas.md#userpublishing). | | What can go wrong? | `401` not signed in; `429` too many requests. Every failure uses the one error envelope. | ## What it does The organisations that claim the account's publishes, its default organisation, and where a publish that names no organisation goes now. ## Responses | Status | Meaning | Body | |---|---|---| | 200 | Where publishes go. | [`UserPublishing`](https://docs.plotwork.dev/api/schemas.md#userpublishing) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`UserPublishing`](https://docs.plotwork.dev/api/schemas.md#userpublishing). | Field | Type | Required | Description | |---|---|---|---| | `defaultOrganization` | `any` | yes | | | `defaultOrganization.id` | `string` | yes | | | `defaultOrganization.slug` | `string` | yes | | | `defaultOrganization.name` | `string` | yes | | | `organizations` | `PublishingClaim[]` | yes | The organisations whose Publishing setting claims the person's publishes, by name. | | `organizations[].id` | `string` | yes | | | `organizations[].slug` | `string` | yes | | | `organizations[].name` | `string` | yes | | | `organizations[].policy` | `"organisation-default" \| "organisation-required"` | yes | What it enforces: its library by default, or its library always. | | `organizations[].via` | `"member" \| "domain"` | yes | member when the person belongs to it; domain when it verified the domain of their address and they have not joined. | | `organizations[].canPublish` | `boolean` | yes | Whether the person may publish to its library: a member who is not a reader. | | `target` | `PublishTarget` | yes | personal, the organisation it goes to and why, choose (several claim it and none is the default), or refused. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Choose your default organisation for publishing > Page: https://docs.plotwork.dev/api/put-user-publishing. Index of every page: https://docs.plotwork.dev/llms.txt The organisation that takes a publish naming none when several claim it, or none with null. `PUT /user/publishing` ## TL;DR | Question | Answer | |---|---| | What does it do? | Choose your default organisation for publishing. | | What does it take? | A JSON body of type [`SetUserPublishingBody`](https://docs.plotwork.dev/api/schemas.md#setuserpublishingbody). | | What does it return? | Status 200 with a body of type [`UserPublishing`](https://docs.plotwork.dev/api/schemas.md#userpublishing). | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` the account is a reader there (orgs.forbidden), or the request came from an origin that may not make changes; `404` no such organisation, or the account is not a member of it; `429` too many requests. Every failure uses the one error envelope. | ## What it does The organisation that takes a publish naming none when several claim it, or none with null. The account must be a member who may publish there. ## Request body The body has the type [`SetUserPublishingBody`](https://docs.plotwork.dev/api/schemas.md#setuserpublishingbody). | Field | Type | Required | Description | |---|---|---|---| | `defaultOrganization` | `string \| null` | yes | An organisation's slug or id, or null for none. The person must be a member who may publish there. at least 1 character; at most 64 characters. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | Where publishes go now. | [`UserPublishing`](https://docs.plotwork.dev/api/schemas.md#userpublishing) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The account is a reader there (orgs.forbidden), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the account is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`UserPublishing`](https://docs.plotwork.dev/api/schemas.md#userpublishing). | Field | Type | Required | Description | |---|---|---|---| | `defaultOrganization` | `any` | yes | | | `defaultOrganization.id` | `string` | yes | | | `defaultOrganization.slug` | `string` | yes | | | `defaultOrganization.name` | `string` | yes | | | `organizations` | `PublishingClaim[]` | yes | The organisations whose Publishing setting claims the person's publishes, by name. | | `organizations[].id` | `string` | yes | | | `organizations[].slug` | `string` | yes | | | `organizations[].name` | `string` | yes | | | `organizations[].policy` | `"organisation-default" \| "organisation-required"` | yes | What it enforces: its library by default, or its library always. | | `organizations[].via` | `"member" \| "domain"` | yes | member when the person belongs to it; domain when it verified the domain of their address and they have not joined. | | `organizations[].canPublish` | `boolean` | yes | Whether the person may publish to its library: a member who is not a reader. | | `target` | `PublishTarget` | yes | personal, the organisation it goes to and why, choose (several claim it and none is the default), or refused. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # List an organisation's folder rules > Page: https://docs.plotwork.dev/api/get-orgs-org-folder-rules. Index of every page: https://docs.plotwork.dev/llms.txt Each restricted folder with the roles and the people let in. `GET /orgs/{org}/folder-rules` ## TL;DR | Question | Answer | |---|---| | What does it do? | List an organisation's folder rules. | | What does it take? | `org` in the path. | | What does it return? | Status 200 with a body of type [`FolderRuleList`](https://docs.plotwork.dev/api/schemas.md#folderrulelist). | | What can go wrong? | `401` not signed in; `403` the caller's role may not read the rules (orgs.forbidden); `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Each restricted folder with the roles and the people let in. The owner and the admins may read them. A rule covers the folder and every folder inside it, and the nearest rule wins. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The rules. | [`FolderRuleList`](https://docs.plotwork.dev/api/schemas.md#folderrulelist) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The caller's role may not read the rules (orgs.forbidden). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`FolderRuleList`](https://docs.plotwork.dev/api/schemas.md#folderrulelist). | Field | Type | Required | Description | |---|---|---|---| | `items` | `FolderRule[]` | yes | By folder, at most 50. | | `items[].folder` | `string` | yes | The restricted folder, in the form diagrams carry in their `folder`. | | `items[].roles` | `("admin" \| "member" \| "reader" \| "key")[]` | yes | The roles let in, apart from the owner, who is always allowed: `admin`, `member`, `reader`, and `key` for the organisation's own API keys. | | `items[].users` | `FolderRuleUser[]` | yes | The people let in by name, whatever their role. | | `items[].users[].userId` | `string` | yes | The member's account id. | | `items[].users[].email` | `string` | yes | The member's address, as the member list shows it. | | `items[].createdAt` | `integer` | yes | Milliseconds since the epoch. When the rule as it now stands was set; replacing a rule sets it again. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Restrict a folder > Page: https://docs.plotwork.dev/api/put-orgs-org-folder-rules. Index of every page: https://docs.plotwork.dev/llms.txt Makes or replaces the rule for a folder and everything inside it. `PUT /orgs/{org}/folder-rules` ## TL;DR | Question | Answer | |---|---| | What does it do? | Restrict a folder. | | What does it take? | A JSON body of type [`SetFolderRuleBody`](https://docs.plotwork.dev/api/schemas.md#setfolderrulebody). | | What does it return? | Status 204 with no body. | | What can go wrong? | `400` the body is not valid, or the rule cannot be stored (orgs.rule_invalid; details.reason is bad_folder, unknown_role, or not_a_member); `401` not signed in; `403` only the owner may (orgs.forbidden), a limit is reached (orgs.rule_limit_reached), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Makes or replaces the rule for a folder and everything inside it. Only the owner, the roles named, and the people named may use it. Empty roles and users lets in the owner alone. Only the owner may write a rule. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Request body The body has the type [`SetFolderRuleBody`](https://docs.plotwork.dev/api/schemas.md#setfolderrulebody). | Field | Type | Required | Description | |---|---|---|---| | `folder` | `string` | yes | The folder the rule restricts, with every folder inside it: names from the outermost in, joined with a slash, with no slash at either end. at least 1 character; at most 405 characters. | | `roles` | `string[]` | yes | Any of admin, member, reader, key. `owner` is not a choice. An empty list, with no people, lets in the owner alone. | | `users` | `string[]` | yes | Account ids of current members, at most 100. A person who leaves is taken out of every rule. | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | The rule is in place. | none | | 400 | The body is not valid, or the rule cannot be stored (orgs.rule_invalid; details.reason is bad_folder, unknown_role, or not_a_member). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | Only the owner may (orgs.forbidden), a limit is reached (orgs.rule_limit_reached), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Remove a folder's rule > Page: https://docs.plotwork.dev/api/delete-orgs-org-folder-rules. Index of every page: https://docs.plotwork.dev/llms.txt Removes the rule for the folder in the folder query, so the folder is open again. `DELETE /orgs/{org}/folder-rules` ## TL;DR | Question | Answer | |---|---| | What does it do? | Remove a folder's rule. | | What does it take? | `org` in the path, and `folder` in the query string. | | What does it return? | Status 204 with no body. | | What can go wrong? | `400` the folder path breaks the rules (orgs.rule_invalid; details.reason is bad_folder); `401` not signed in; `403` only the owner may (orgs.forbidden), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Removes the rule for the folder in the folder query, so the folder is open again. A folder with no rule answers the same, 204. Only the owner may. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Query parameters | Field | Type | Required | Description | |---|---|---|---| | `folder` | `string` | yes | The folder the rule restricts, with every folder inside it: names from the outermost in, joined with a slash, with no slash at either end. | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | The rule is gone, or there was none. | none | | 400 | The folder path breaks the rules (orgs.rule_invalid; details.reason is bad_folder). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | Only the owner may (orgs.forbidden), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # List an organisation's API keys > Page: https://docs.plotwork.dev/api/get-orgs-org-keys. Index of every page: https://docs.plotwork.dev/llms.txt The organisation's active keys, newest first. `GET /orgs/{org}/keys` ## TL;DR | Question | Answer | |---|---| | What does it do? | List an organisation's API keys. | | What does it take? | `org` in the path. | | What does it return? | Status 200 with a body of type [`OrgApiKeyList`](https://docs.plotwork.dev/api/schemas.md#orgapikeylist). | | What can go wrong? | `401` not signed in; `403` a member may not see the keys (orgs.forbidden); `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does The organisation's active keys, newest first. A key is shown as its first characters and its last four. The owner and an admin may look. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The active keys. | [`OrgApiKeyList`](https://docs.plotwork.dev/api/schemas.md#orgapikeylist) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A member may not see the keys (orgs.forbidden). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`OrgApiKeyList`](https://docs.plotwork.dev/api/schemas.md#orgapikeylist). | Field | Type | Required | Description | |---|---|---|---| | `items` | `OrgApiKeySummary[]` | yes | | | `items[].id` | `string` | yes | | | `items[].name` | `string` | yes | | | `items[].prefix` | `string` | yes | | | `items[].last4` | `string` | yes | | | `items[].createdAt` | `integer` | yes | Milliseconds since the epoch. | | `items[].lastUsedAt` | `integer \| null` | yes | Milliseconds since the epoch, or null if never used. | | `items[].revealable` | `boolean` | yes | True when the full key can be shown again with the reveal route. False for a key made before that was possible, or on a server that cannot seal keys. | | `items[].scopes` | `("diagrams" \| "audit:read")[]` | yes | `diagrams`: read the library, publish, and change its diagrams, which every key made before scopes holds. `audit:read`: read and export the audit log, and nothing else unless `diagrams` is also given. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Create an organisation API key > Page: https://docs.plotwork.dev/api/post-orgs-org-keys. Index of every page: https://docs.plotwork.dev/llms.txt Makes a key that belongs to the organisation and returns its full value. `POST /orgs/{org}/keys` ## TL;DR | Question | Answer | |---|---| | What does it do? | Create an organisation API key. | | What does it take? | A JSON body of type [`CreateOrgApiKeyBody`](https://docs.plotwork.dev/api/schemas.md#createorgapikeybody). | | What does it return? | Status 201 with a body of type [`CreatedOrgApiKey`](https://docs.plotwork.dev/api/schemas.md#createdorgapikey). | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` a member may not make keys (orgs.forbidden), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `409` the organisation already holds the most keys allowed; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Makes a key that belongs to the organisation and returns its full value. It acts only in this organisation, as its scopes allow (`diagrams` by default, `audit:read` for the audit log), and spends the owner's daily allowance. A sealed copy is kept when the server can, so the key can be revealed again; otherwise this answer is the only time it is shown. At most ten active keys. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Request body The body has the type [`CreateOrgApiKeyBody`](https://docs.plotwork.dev/api/schemas.md#createorgapikeybody). | Field | Type | Required | Description | |---|---|---|---| | `name` | `string` | yes | at least 1 character; at most 60 characters; pattern `\S`. | | `scopes` | `("diagrams" \| "audit:read")[]` | no | What the key may do. Left out, `["diagrams"]`. A key for a SIEM takes `["audit:read"]` alone. | ## Responses | Status | Meaning | Body | |---|---|---| | 201 | The new key, including its full value. | [`CreatedOrgApiKey`](https://docs.plotwork.dev/api/schemas.md#createdorgapikey) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A member may not make keys (orgs.forbidden), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | The organisation already holds the most keys allowed. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 201 answer has the type [`CreatedOrgApiKey`](https://docs.plotwork.dev/api/schemas.md#createdorgapikey). | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `name` | `string` | yes | | | `prefix` | `string` | yes | | | `last4` | `string` | yes | | | `createdAt` | `integer` | yes | Milliseconds since the epoch. | | `lastUsedAt` | `integer \| null` | yes | Milliseconds since the epoch, or null if never used. | | `revealable` | `boolean` | yes | True when the full key can be shown again with the reveal route. False for a key made before that was possible, or on a server that cannot seal keys. | | `key` | `string` | yes | The full key. When `revealable` is false this is the only time it is shown, so store it now. | | `scopes` | `("diagrams" \| "audit:read")[]` | yes | `diagrams`: read the library, publish, and change its diagrams, which every key made before scopes holds. `audit:read`: read and export the audit log, and nothing else unless `diagrams` is also given. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Reveal an organisation API key > Page: https://docs.plotwork.dev/api/post-orgs-org-keys-id-reveal. Index of every page: https://docs.plotwork.dev/llms.txt Shows the full value of one of the organisation's keys again, to the owner or an admin. `POST /orgs/{org}/keys/{id}/reveal` ## TL;DR | Question | Answer | |---|---| | What does it do? | Reveal an organisation API key. | | What does it take? | `org`, `id` in the path. | | What does it return? | Status 200 with a body of type [`RevealedApiKey`](https://docs.plotwork.dev/api/schemas.md#revealedapikey). | | What can go wrong? | `401` not signed in; `403` a member may not reveal keys (orgs.forbidden), or the request came from an origin that may not make changes; `404` no such organisation or key, or the caller is not a member of the organisation; `409` this key has no sealed copy to show; `429` too many requests; `503` revealing keys is not set up on this server, or organisations are not. Every failure uses the one error envelope. | ## What it does Shows the full value of one of the organisation's keys again, to the owner or an admin. Only a key with a sealed copy can be revealed. Audited by key id, never by value. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | | `id` | `string` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The key, with its full value. | [`RevealedApiKey`](https://docs.plotwork.dev/api/schemas.md#revealedapikey) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A member may not reveal keys (orgs.forbidden), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation or key, or the caller is not a member of the organisation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | This key has no sealed copy to show. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Revealing keys is not set up on this server, or organisations are not. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`RevealedApiKey`](https://docs.plotwork.dev/api/schemas.md#revealedapikey). | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `key` | `string` | yes | The full key. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Revoke an organisation API key > Page: https://docs.plotwork.dev/api/delete-orgs-org-keys-id. Index of every page: https://docs.plotwork.dev/llms.txt Stops the key working at once. `DELETE /orgs/{org}/keys/{id}` ## TL;DR | Question | Answer | |---|---| | What does it do? | Revoke an organisation API key. | | What does it take? | `org`, `id` in the path. | | What does it return? | Status 204 with no body. | | What can go wrong? | `401` not signed in; `403` a member may not revoke keys (orgs.forbidden), or the request came from an origin that may not make changes; `404` no such organisation or key, or the caller is not a member of the organisation; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Stops the key working at once. A key that is not this organisation's, or is already revoked, is a 404. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | | `id` | `string` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Revoked. | none | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A member may not revoke keys (orgs.forbidden), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation or key, or the caller is not a member of the organisation. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Read an organisation's brand theme > Page: https://docs.plotwork.dev/api/get-orgs-org-brand. Index of every page: https://docs.plotwork.dev/llms.txt The saved brand colors, which of them were chosen, whether the hosted service paints with them (a brand is saved and the owner's plan includes brand themes), whether the caller may change them, and the plan. `GET /orgs/{org}/brand` ## TL;DR | Question | Answer | |---|---| | What does it do? | Read an organisation's brand theme. | | What does it take? | `org` in the path. | | What does it return? | Status 200 with a body of type [`OrgBrand`](https://docs.plotwork.dev/api/schemas.md#orgbrand). | | What can go wrong? | `401` not signed in; `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does The saved brand colors, which of them were chosen, whether the hosted service paints with them (a brand is saved and the owner's plan includes brand themes), whether the caller may change them, and the plan. Every member may read it. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The brand. | [`OrgBrand`](https://docs.plotwork.dev/api/schemas.md#orgbrand) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`OrgBrand`](https://docs.plotwork.dev/api/schemas.md#orgbrand). | Field | Type | Required | Description | |---|---|---|---| | `brand` | `BrandColors` | yes | The saved brand, or null when none is saved. | | `brand.primary` | `string` | yes | Lowercase #rrggbb. | | `brand.secondary` | `string` | yes | Lowercase #rrggbb. | | `brand.accent` | `string` | yes | Lowercase #rrggbb. | | `chosen` | `("primary" \| "secondary" \| "accent")[]` | yes | The colors the person chose. The others are made from the primary. | | `active` | `boolean` | yes | True when a brand is saved and the owner's plan includes brand themes, so the hosted service paints with it. | | `canEdit` | `boolean` | yes | True when the caller's role may save, remove and preview a brand: the owner and admins. | | `plan` | `"free" \| "pro" \| "enterprise"` | yes | The organisation's plan, which is its owner's. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Save an organisation's brand theme > Page: https://docs.plotwork.dev/api/put-orgs-org-brand. Index of every page: https://docs.plotwork.dev/llms.txt Saves one to three colors. `PUT /orgs/{org}/brand` ## TL;DR | Question | Answer | |---|---| | What does it do? | Save an organisation's brand theme. | | What does it take? | A JSON body of type [`BrandBody`](https://docs.plotwork.dev/api/schemas.md#brandbody). | | What does it return? | Status 200 with a body of type [`SavedBrand`](https://docs.plotwork.dev/api/schemas.md#savedbrand). | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` the caller's role may not (orgs.forbidden), the owner's plan does not include brand themes (plan.brand_not_included), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `422` a color is not valid, or there is no primary (brand.invalid, with every issue in details); `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Saves one to three colors. The hosted service paints every diagram it draws for the organisation in a palette made from them; status colors and official cloud icons never change. The owner and admins may, on a plan with brand themes. 422 brand.invalid carries every issue of the check. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Request body The body has the type [`BrandBody`](https://docs.plotwork.dev/api/schemas.md#brandbody). | Field | Type | Required | Description | |---|---|---|---| | `primary` | `string \| null` | no | The main brand color, as #rrggbb or #rgb in any case. Takes the place of the blue tone. A brand needs one. at most 32 characters. | | `secondary` | `string \| null` | no | Takes the place of the violet tone. Left out or null, it is made from the primary by turning its hue, and follows the primary. at most 32 characters. | | `accent` | `string \| null` | no | Takes the place of the aqua tone. Left out or null, it is made from the primary by turning its hue, and follows the primary. at most 32 characters. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The saved brand and its check. | [`SavedBrand`](https://docs.plotwork.dev/api/schemas.md#savedbrand) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The caller's role may not (orgs.forbidden), the owner's plan does not include brand themes (plan.brand_not_included), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 422 | A color is not valid, or there is no primary (brand.invalid, with every issue in details). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`SavedBrand`](https://docs.plotwork.dev/api/schemas.md#savedbrand). | Field | Type | Required | Description | |---|---|---|---| | `brand` | `any` | yes | | | `brand.primary` | `string` | yes | Lowercase #rrggbb. | | `brand.secondary` | `string` | yes | Lowercase #rrggbb. | | `brand.accent` | `string` | yes | Lowercase #rrggbb. | | `chosen` | `("primary" \| "secondary" \| "accent")[]` | yes | | | `check` | `BrandCheck` | yes | | | `check.issues` | `BrandIssue[]` | yes | | | `check.issues[].role` | `"primary" \| "secondary" \| "accent"` | yes | | | `check.issues[].severity` | `"error" \| "warning" \| "note"` | yes | An error stops a save. A warning and a note are advice. | | `check.issues[].code` | `"not-a-color" \| "missing-primary" \| "reads-as-status" \| "too-similar" \| "adjusted"` | yes | | | `check.issues[].message` | `string` | yes | | | `check.swatches` | `object \| null` | yes | The color each scheme draws for each role, made darker or lighter where it would not keep 3:1 against the surface. | | `check.swatches.light` | `object` | yes | | | `check.swatches.dark` | `object` | yes | | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Remove an organisation's brand theme > Page: https://docs.plotwork.dev/api/delete-orgs-org-brand. Index of every page: https://docs.plotwork.dev/llms.txt Removes the saved colors, so diagrams are drawn in the built-in theme again. `DELETE /orgs/{org}/brand` ## TL;DR | Question | Answer | |---|---| | What does it do? | Remove an organisation's brand theme. | | What does it take? | `org` in the path. | | What does it return? | Status 204 with no body. | | What can go wrong? | `401` not signed in; `403` the caller's role may not (orgs.forbidden), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Removes the saved colors, so diagrams are drawn in the built-in theme again. 204 also when there was none. The owner and admins may. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Removed. | none | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The caller's role may not (orgs.forbidden), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Preview brand colors > Page: https://docs.plotwork.dev/api/post-orgs-org-brand-preview. Index of every page: https://docs.plotwork.dev/llms.txt Draws two fixed sample diagrams in the colors given, in light and dark, with the check. `POST /orgs/{org}/brand/preview` ## TL;DR | Question | Answer | |---|---| | What does it do? | Preview brand colors. | | What does it take? | A JSON body of type [`BrandBody`](https://docs.plotwork.dev/api/schemas.md#brandbody). | | What does it return? | Status 200 with a body of type [`BrandPreview`](https://docs.plotwork.dev/api/schemas.md#brandpreview). | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` the caller's role may not (orgs.forbidden), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `422` a color is not valid, or there is no primary (brand.invalid, with every issue in details); `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Draws two fixed sample diagrams in the colors given, in light and dark, with the check. Works on every plan. The owner and admins may. Stores nothing. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Request body The body has the type [`BrandBody`](https://docs.plotwork.dev/api/schemas.md#brandbody). | Field | Type | Required | Description | |---|---|---|---| | `primary` | `string \| null` | no | The main brand color, as #rrggbb or #rgb in any case. Takes the place of the blue tone. A brand needs one. at most 32 characters. | | `secondary` | `string \| null` | no | Takes the place of the violet tone. Left out or null, it is made from the primary by turning its hue, and follows the primary. at most 32 characters. | | `accent` | `string \| null` | no | Takes the place of the aqua tone. Left out or null, it is made from the primary by turning its hue, and follows the primary. at most 32 characters. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The check and the sample pictures. | [`BrandPreview`](https://docs.plotwork.dev/api/schemas.md#brandpreview) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The caller's role may not (orgs.forbidden), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 422 | A color is not valid, or there is no primary (brand.invalid, with every issue in details). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`BrandPreview`](https://docs.plotwork.dev/api/schemas.md#brandpreview). | Field | Type | Required | Description | |---|---|---|---| | `check` | `BrandCheck` | yes | | | `check.issues` | `BrandIssue[]` | yes | | | `check.issues[].role` | `"primary" \| "secondary" \| "accent"` | yes | | | `check.issues[].severity` | `"error" \| "warning" \| "note"` | yes | An error stops a save. A warning and a note are advice. | | `check.issues[].code` | `"not-a-color" \| "missing-primary" \| "reads-as-status" \| "too-similar" \| "adjusted"` | yes | | | `check.issues[].message` | `string` | yes | | | `check.swatches` | `object \| null` | yes | The color each scheme draws for each role, made darker or lighter where it would not keep 3:1 against the surface. | | `check.swatches.light` | `object` | yes | | | `check.swatches.dark` | `object` | yes | | | `pictures` | `BrandPreviewPicture[]` | yes | | | `pictures[].id` | `string` | yes | | | `pictures[].title` | `string` | yes | | | `pictures[].light` | `string` | yes | The sample as an SVG in the light scheme. | | `pictures[].dark` | `string` | yes | The sample as an SVG in the dark scheme. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Read the brand of your own calls > Page: https://docs.plotwork.dev/api/get-user-brand. Index of every page: https://docs.plotwork.dev/llms.txt The organisation whose brand the account's own calls use, or null, and the organisations the account belongs to that have a saved brand. `GET /user/brand` ## TL;DR | Question | Answer | |---|---| | What does it do? | Read the brand of your own calls. | | What does it take? | Nothing. | | What does it return? | Status 200 with a body of type [`AccountBrand`](https://docs.plotwork.dev/api/schemas.md#accountbrand). | | What can go wrong? | `401` not signed in; `429` too many requests. Every failure uses the one error envelope. | ## What it does The organisation whose brand the account's own calls use, or null, and the organisations the account belongs to that have a saved brand. An organisation key always uses its own organisation's brand. ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The choice and the organisations to choose from. | [`AccountBrand`](https://docs.plotwork.dev/api/schemas.md#accountbrand) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`AccountBrand`](https://docs.plotwork.dev/api/schemas.md#accountbrand). | Field | Type | Required | Description | |---|---|---|---| | `chosen` | `object \| null` | yes | The organisation whose brand and custom icons the account's own calls use, or null. | | `chosen.id` | `string` | yes | | | `chosen.slug` | `string` | yes | | | `chosen.name` | `string` | yes | | | `available` | `object[]` | yes | The organisations the account belongs to that have a saved brand or custom icons, by name. | | `available[].id` | `string` | yes | | | `available[].slug` | `string` | yes | | | `available[].name` | `string` | yes | | | `available[].active` | `boolean` | yes | True when the organisation has a saved brand and the owner's plan includes brand themes. | | `available[].iconsActive` | `boolean` | yes | True when the organisation has custom icons and the owner's plan includes them. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Choose the brand of your own calls > Page: https://docs.plotwork.dev/api/put-user-brand. Index of every page: https://docs.plotwork.dev/llms.txt Chooses the organisation whose brand the account's own calls use, or none with null. `PUT /user/brand` ## TL;DR | Question | Answer | |---|---| | What does it do? | Choose the brand of your own calls. | | What does it take? | A JSON body of type [`ChooseBrandBody`](https://docs.plotwork.dev/api/schemas.md#choosebrandbody). | | What does it return? | Status 204 with no body. | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` the request came from an origin that may not make changes; `404` no such organisation, the account is not a member of it, or it has no saved brand; `429` too many requests. Every failure uses the one error envelope. | ## What it does Chooses the organisation whose brand the account's own calls use, or none with null. The account must be a member and the organisation must have a saved brand. ## Request body The body has the type [`ChooseBrandBody`](https://docs.plotwork.dev/api/schemas.md#choosebrandbody). | Field | Type | Required | Description | |---|---|---|---| | `organization` | `string \| null` | yes | An organisation's id or slug, or null for no brand. The account must be a member and the organisation must have a saved brand. at least 1 character; at most 64 characters. | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Chosen. | none | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, the account is not a member of it, or it has no saved brand. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Read an organisation's style rules > Page: https://docs.plotwork.dev/api/get-orgs-org-style-rules. Index of every page: https://docs.plotwork.dev/llms.txt The saved style rules, as written and parsed, whether CI applies them (rules are saved and the owner's plan includes them), whether the caller may change them, and the plan. `GET /orgs/{org}/style-rules` ## TL;DR | Question | Answer | |---|---| | What does it do? | Read an organisation's style rules. | | What does it take? | `org` in the path. | | What does it return? | Status 200 with a body of type [`OrgStyleRules`](https://docs.plotwork.dev/api/schemas.md#orgstylerules). | | What can go wrong? | `401` not signed in, or the key is not valid; `404` no such organisation, the caller is not a member of it, or the key is another organisation's; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does The saved style rules, as written and parsed, whether CI applies them (rules are saved and the owner's plan includes them), whether the caller may change them, and the plan. Every member may read them, and so may a key of the organisation, which is how the CLI and the GitHub Action read them. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The rules. | [`OrgStyleRules`](https://docs.plotwork.dev/api/schemas.md#orgstylerules) | | 401 | Not signed in, or the key is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, the caller is not a member of it, or the key is another organisation's. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`OrgStyleRules`](https://docs.plotwork.dev/api/schemas.md#orgstylerules). | Field | Type | Required | Description | |---|---|---|---| | `rules` | `StyleRules \| null` | yes | The saved rules as the engine read them (the format of docs/architecture/repository-settings.md), or null when none are saved. | | `text` | `string \| null` | yes | The saved rules as the person wrote them, or null. | | `active` | `boolean` | yes | True when rules are saved and the owner's plan includes saved style rules, so CI that reads them applies them. | | `canEdit` | `boolean` | yes | True when the caller's role may save and remove the rules. | | `plan` | `"free" \| "pro" \| "enterprise"` | yes | The organisation's plan, which is its owner's. | | `updatedAt` | `integer \| null` | yes | When the rules were last saved, in milliseconds since the epoch, or null. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Save an organisation's style rules > Page: https://docs.plotwork.dev/api/put-orgs-org-style-rules. Index of every page: https://docs.plotwork.dev/llms.txt Saves the rules file as JSON text, for CI to read with an organisation key. `PUT /orgs/{org}/style-rules` ## TL;DR | Question | Answer | |---|---| | What does it do? | Save an organisation's style rules. | | What does it take? | A JSON body of type [`StyleRulesBody`](https://docs.plotwork.dev/api/schemas.md#stylerulesbody). | | What does it return? | Status 200 with a body of type [`OrgStyleRules`](https://docs.plotwork.dev/api/schemas.md#orgstylerules). | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` the caller's role may not (orgs.forbidden), the owner's plan does not include saved style rules (plan.style_rules_not_included), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `422` the rules are not valid (style_rules.invalid, with every problem in details); `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Saves the rules file as JSON text, for CI to read with an organisation key. The owner and admins may, on a plan with saved style rules. 422 style_rules.invalid carries every problem with its line, column and hint. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Request body The body has the type [`StyleRulesBody`](https://docs.plotwork.dev/api/schemas.md#stylerulesbody). | Field | Type | Required | Description | |---|---|---|---| | `text` | `string` | yes | The rules file as JSON text, at most 32768 characters. The style rules guide lists its fields. at least 1 character; at most 32768 characters. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The saved rules. | [`OrgStyleRules`](https://docs.plotwork.dev/api/schemas.md#orgstylerules) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The caller's role may not (orgs.forbidden), the owner's plan does not include saved style rules (plan.style_rules_not_included), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 422 | The rules are not valid (style_rules.invalid, with every problem in details). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`OrgStyleRules`](https://docs.plotwork.dev/api/schemas.md#orgstylerules). | Field | Type | Required | Description | |---|---|---|---| | `rules` | `StyleRules \| null` | yes | The saved rules as the engine read them (the format of docs/architecture/repository-settings.md), or null when none are saved. | | `text` | `string \| null` | yes | The saved rules as the person wrote them, or null. | | `active` | `boolean` | yes | True when rules are saved and the owner's plan includes saved style rules, so CI that reads them applies them. | | `canEdit` | `boolean` | yes | True when the caller's role may save and remove the rules. | | `plan` | `"free" \| "pro" \| "enterprise"` | yes | The organisation's plan, which is its owner's. | | `updatedAt` | `integer \| null` | yes | When the rules were last saved, in milliseconds since the epoch, or null. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Remove an organisation's style rules > Page: https://docs.plotwork.dev/api/delete-orgs-org-style-rules. Index of every page: https://docs.plotwork.dev/llms.txt Removes the saved rules. `DELETE /orgs/{org}/style-rules` ## TL;DR | Question | Answer | |---|---| | What does it do? | Remove an organisation's style rules. | | What does it take? | `org` in the path. | | What does it return? | Status 204 with no body. | | What can go wrong? | `401` not signed in; `403` the caller's role may not (orgs.forbidden), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Removes the saved rules. 204 also when there were none. The owner and admins may. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Removed. | none | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The caller's role may not (orgs.forbidden), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # List an organisation's custom icons > Page: https://docs.plotwork.dev/api/get-orgs-org-icons. Index of every page: https://docs.plotwork.dev/llms.txt The organisation's own icons, each with its org: reference, aliases, and checked picture, with whether the hosted service draws them (the owner's plan includes custom icons), whether the caller may change them, the plan, and its limit. `GET /orgs/{org}/icons` ## TL;DR | Question | Answer | |---|---| | What does it do? | List an organisation's custom icons. | | What does it take? | `org` in the path. | | What does it return? | Status 200 with a body of type [`OrgIcons`](https://docs.plotwork.dev/api/schemas.md#orgicons). | | What can go wrong? | `401` not signed in, or the key is not valid; `404` no such organisation, the caller is not a member of it, or the key is another organisation's; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does The organisation's own icons, each with its org: reference, aliases, and checked picture, with whether the hosted service draws them (the owner's plan includes custom icons), whether the caller may change them, the plan, and its limit. Every member may read them, and so may a key of the organisation. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The icons. | [`OrgIcons`](https://docs.plotwork.dev/api/schemas.md#orgicons) | | 401 | Not signed in, or the key is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, the caller is not a member of it, or the key is another organisation's. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`OrgIcons`](https://docs.plotwork.dev/api/schemas.md#orgicons). | Field | Type | Required | Description | |---|---|---|---| | `icons` | `OrgIcon[]` | yes | | | `icons[].name` | `string` | yes | | | `icons[].ref` | `string` | yes | What a diagram writes to use it. | | `icons[].aliases` | `string[]` | yes | | | `icons[].mediaType` | `"image/svg+xml" \| "image/png"` | yes | | | `icons[].image` | `string` | yes | The checked picture as a data: address, for a preview. | | `icons[].width` | `number` | yes | | | `icons[].height` | `number` | yes | | | `icons[].bytes` | `integer` | yes | | | `icons[].updatedAt` | `integer` | yes | Milliseconds since the epoch. | | `active` | `boolean` | yes | True when the owner's plan includes custom icons, so the hosted service draws them. | | `canEdit` | `boolean` | yes | True when the caller's role may upload, replace and delete icons. | | `plan` | `"free" \| "pro" \| "enterprise"` | yes | | | `limit` | `integer` | yes | How many icons the plan lets the organisation keep. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Upload or replace a custom icon > Page: https://docs.plotwork.dev/api/put-orgs-org-icons-name. Index of every page: https://docs.plotwork.dev/llms.txt Saves one SVG or PNG icon (at most 32 KB) under a name diagrams use as icon=org:<name> on the hosted service, with up to ten aliases. `PUT /orgs/{org}/icons/{name}` ## TL;DR | Question | Answer | |---|---| | What does it do? | Upload or replace a custom icon. | | What does it take? | A JSON body of type [`IconBody`](https://docs.plotwork.dev/api/schemas.md#iconbody). | | What does it return? | Status 200 with a body of type [`SavedIcon`](https://docs.plotwork.dev/api/schemas.md#savedicon). | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` the caller's role may not (orgs.forbidden), the owner's plan does not include custom icons (plan.icons_not_included), the organisation holds the most icons it may (orgs.icon_limit_reached), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `413` the body is too large; `422` the file, the name or an alias does not check (icon.invalid, with every issue in details); `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Saves one SVG or PNG icon (at most 32 KB) under a name diagrams use as icon=org:<name> on the hosted service, with up to ten aliases. An SVG is rewritten from an allowlist; anything that could run or load is refused. The owner and admins may, on a plan with custom icons. 422 icon.invalid carries every issue. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | | `name` | `string` | yes | The name after org:, lowercase words joined by hyphens. | ## Request body The body has the type [`IconBody`](https://docs.plotwork.dev/api/schemas.md#iconbody). | Field | Type | Required | Description | |---|---|---|---| | `svg` | `string` | no | The SVG file's text, at most 32 KB. It is rewritten from an allowlist before it is stored. at most 49152 characters. | | `png` | `string` | no | The PNG file's bytes in base64, at most 32 KB and 1024 pixels on a side. at most 49152 characters. | | `aliases` | `string[]` | no | Other names search and automatic matching find the icon by, at most 10. Left out, the icon has none. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The saved icon, and what the check left out of the file. | [`SavedIcon`](https://docs.plotwork.dev/api/schemas.md#savedicon) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The caller's role may not (orgs.forbidden), the owner's plan does not include custom icons (plan.icons_not_included), the organisation holds the most icons it may (orgs.icon_limit_reached), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is too large. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 422 | The file, the name or an alias does not check (icon.invalid, with every issue in details). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`SavedIcon`](https://docs.plotwork.dev/api/schemas.md#savedicon). | Field | Type | Required | Description | |---|---|---|---| | `icon` | `OrgIcon` | yes | | | `icon.name` | `string` | yes | | | `icon.ref` | `string` | yes | What a diagram writes to use it. | | `icon.aliases` | `string[]` | yes | | | `icon.mediaType` | `"image/svg+xml" \| "image/png"` | yes | | | `icon.image` | `string` | yes | The checked picture as a data: address, for a preview. | | `icon.width` | `number` | yes | | | `icon.height` | `number` | yes | | | `icon.bytes` | `integer` | yes | | | `icon.updatedAt` | `integer` | yes | Milliseconds since the epoch. | | `removed` | `string[]` | yes | What the check left out of the file because it draws nothing, such as editor metadata. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Delete a custom icon > Page: https://docs.plotwork.dev/api/delete-orgs-org-icons-name. Index of every page: https://docs.plotwork.dev/llms.txt Deletes one icon. `DELETE /orgs/{org}/icons/{name}` ## TL;DR | Question | Answer | |---|---| | What does it do? | Delete a custom icon. | | What does it take? | `org`, `name` in the path. | | What does it return? | Status 204 with no body. | | What can go wrong? | `401` not signed in; `403` the caller's role may not (orgs.forbidden), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Deletes one icon. A diagram that still names it is an error on the hosted service; a picture already published keeps it. 204 also when there was none. The owner and admins may. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | | `name` | `string` | yes | The name after org:, lowercase words joined by hyphens. | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Deleted. | none | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The caller's role may not (orgs.forbidden), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Preview a custom icon in a diagram > Page: https://docs.plotwork.dev/api/get-orgs-org-icons-name-preview. Index of every page: https://docs.plotwork.dev/llms.txt Draws one icon on a box of a small fixed diagram, in light and dark, in the organisation's brand while it is active. `GET /orgs/{org}/icons/{name}/preview` ## TL;DR | Question | Answer | |---|---| | What does it do? | Preview a custom icon in a diagram. | | What does it take? | `org`, `name` in the path. | | What does it return? | Status 200 with a body of type [`IconPreview`](https://docs.plotwork.dev/api/schemas.md#iconpreview). | | What can go wrong? | `401` not signed in; `404` no such organisation, the caller is not a member of it, or no such icon; `429` too many requests; `503` organisations are not set up on this server, or the drawing engine is not available (engine.unavailable). Every failure uses the one error envelope. | ## What it does Draws one icon on a box of a small fixed diagram, in light and dark, in the organisation's brand while it is active. Every member may. Stores nothing. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | | `name` | `string` | yes | The name after org:, lowercase words joined by hyphens. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The source and the pictures. | [`IconPreview`](https://docs.plotwork.dev/api/schemas.md#iconpreview) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, the caller is not a member of it, or no such icon. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server, or the drawing engine is not available (engine.unavailable). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`IconPreview`](https://docs.plotwork.dev/api/schemas.md#iconpreview). | Field | Type | Required | Description | |---|---|---|---| | `ref` | `string` | yes | What a diagram writes to use the icon. | | `source` | `string` | yes | The diagram source the preview draws, which uses the icon on one box. | | `light` | `string` | yes | The preview drawn on light, as SVG text. | | `dark` | `string` | yes | The preview drawn on dark, as SVG text. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Read an organisation's avatar > Page: https://docs.plotwork.dev/api/get-orgs-org-avatar. Index of every page: https://docs.plotwork.dev/llms.txt The uploaded avatar as the check stored it, with nosniff, a policy that lets it draw and nothing else, and a private cache keyed by the version in the address. `GET /orgs/{org}/avatar` ## TL;DR | Question | Answer | |---|---| | What does it do? | Read an organisation's avatar. | | What does it take? | `org` in the path, and `v` in the query string. | | What does it return? | Status 200 with no body. | | What can go wrong? | `401` not signed in, or the key is not valid; `404` no such organisation, the caller is not a member of it, or it has no avatar; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does The uploaded avatar as the check stored it, with nosniff, a policy that lets it draw and nothing else, and a private cache keyed by the version in the address. A 404 when there is none. Every member may read it, and so may a key of the organisation. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Query parameters | Field | Type | Required | Description | |---|---|---|---| | `v` | `string` | no | The version from the organisation JSON, so a new picture is a new address. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The avatar file. | none | | 401 | Not signed in, or the key is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, the caller is not a member of it, or it has no avatar. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Upload or replace an organisation's avatar > Page: https://docs.plotwork.dev/api/put-orgs-org-avatar. Index of every page: https://docs.plotwork.dev/llms.txt Saves one SVG, PNG, APNG, GIF or WebP avatar, still or animated (at most 256 KB, 512 pixels, 200 frames). `PUT /orgs/{org}/avatar` ## TL;DR | Question | Answer | |---|---| | What does it do? | Upload or replace an organisation's avatar. | | What does it take? | A JSON body of type [`AvatarBody`](https://docs.plotwork.dev/api/schemas.md#avatarbody). | | What does it return? | Status 200 with a body of type [`SavedAvatar`](https://docs.plotwork.dev/api/schemas.md#savedavatar). | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` the caller's role may not (orgs.forbidden), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `413` the body is too large; `422` the file does not check (avatar.invalid, with every issue in details); `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Saves one SVG, PNG, APNG, GIF or WebP avatar, still or animated (at most 256 KB, 512 pixels, 200 frames). An SVG is rewritten from an allowlist that keeps safe SMIL animation; a raster file keeps only the parts that draw. Every plan; the owner and admins. 422 avatar.invalid carries every issue. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Request body The body has the type [`AvatarBody`](https://docs.plotwork.dev/api/schemas.md#avatarbody). | Field | Type | Required | Description | |---|---|---|---| | `svg` | `string` | no | The SVG file's text, at most 256 KB, still or animated with SMIL. It is rewritten from an allowlist before it is stored. at most 368640 characters. | | `image` | `string` | no | A PNG (APNG included), GIF or WebP file's bytes in base64, still or animated: at most 256 KB, 512 pixels on a side, and 200 frames. The format is read from the bytes. at most 368640 characters. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The avatar as the organisation JSON now carries it, and what the check left out of the file. | [`SavedAvatar`](https://docs.plotwork.dev/api/schemas.md#savedavatar) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The caller's role may not (orgs.forbidden), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is too large. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 422 | The file does not check (avatar.invalid, with every issue in details). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`SavedAvatar`](https://docs.plotwork.dev/api/schemas.md#savedavatar). | Field | Type | Required | Description | |---|---|---|---| | `avatar` | `OrgAvatar` | yes | The organisation's uploaded avatar, or null when it has none and a mark made from its id stands in. | | `avatar.version` | `string` | yes | Changes with every upload. Put it in the address (?v=) so a new picture is a new address. | | `avatar.contentType` | `"image/svg+xml" \| "image/png" \| "image/gif" \| "image/webp"` | yes | | | `avatar.animated` | `boolean` | yes | True when it moves, so a page can hold it still for a person who asked for less motion. | | `removed` | `string[]` | yes | What the check left out of the file because it draws nothing, such as editor metadata, EXIF or XMP. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Remove an organisation's avatar > Page: https://docs.plotwork.dev/api/delete-orgs-org-avatar. Index of every page: https://docs.plotwork.dev/llms.txt Deletes the uploaded avatar, so the mark made from the organisation's id shows again. `DELETE /orgs/{org}/avatar` ## TL;DR | Question | Answer | |---|---| | What does it do? | Remove an organisation's avatar. | | What does it take? | `org` in the path. | | What does it return? | Status 204 with no body. | | What can go wrong? | `401` not signed in; `403` the caller's role may not (orgs.forbidden), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Deletes the uploaded avatar, so the mark made from the organisation's id shows again. 204 also when there was none. The owner and admins may. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Removed. | none | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The caller's role may not (orgs.forbidden), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Report a drift run from CI > Page: https://docs.plotwork.dev/api/post-orgs-org-drift-runs. Index of every page: https://docs.plotwork.dev/llms.txt What one run of the GitHub Action's generate command found in one repository: per source its status, change counts, errors and owners. `POST /orgs/{org}/drift/runs` ## TL;DR | Question | Answer | |---|---| | What does it do? | Report a drift run from CI. | | What does it take? | A JSON body of type [`DriftRunBody`](https://docs.plotwork.dev/api/schemas.md#driftrunbody). | | What does it return? | Status 201 with a body of type [`DriftRunReceipt`](https://docs.plotwork.dev/api/schemas.md#driftrunreceipt). | | What can go wrong? | `400` the body is not valid; `401` no key, or the key is not valid; `403` not an organisation key (orgs.org_key_required), or the owner's plan does not include the drift dashboard (plan.drift_dashboard_not_included); `404` no such organisation, or the key is another organisation's; `413` the body is larger than 1 mib; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does What one run of the GitHub Action's generate command found in one repository: per source its status, change counts, errors and owners. Metadata only, never a file or diagram source. An organisation key only; `_` as `{org}` means the key's own organisation. On a plan with the drift dashboard. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Request body The body has the type [`DriftRunBody`](https://docs.plotwork.dev/api/schemas.md#driftrunbody). | Field | Type | Required | Description | |---|---|---|---| | `repository` | `string` | yes | The repository the run checked, as owner/name. at most 140 characters; pattern `^[A-Za-z0-9][A-Za-z0-9-]{0,38}\/[A-Za-z0-9._-]{1,100}$`. | | `ref` | `string \| null` | yes | The git ref the run checked, or null. at least 1 character; at most 255 characters; pattern `^[^\p{Cc}]+$/u`. | | `sha` | `string \| null` | yes | The commit the run checked, or null. pattern `^[0-9a-fA-F]{7,64}$`. | | `event` | `string \| null` | yes | The event that started the run, or null. at least 1 character; at most 64 characters; pattern `^[a-z][a-z0-9_]*$`. | | `runUrl` | `string \| null` | yes | The run on GitHub. An https address of at most 500 characters, or null. at most 500 characters. | | `pullRequestUrl` | `string \| null` | yes | The pull request the run opened or checked. An https address of at most 500 characters, or null. at most 500 characters. | | `sources` | `DriftSourceBody[]` | yes | Every source the run checked, at most 250. A source of the repository that a run leaves out is removed from the dashboard. | | `sources[].name` | `string` | yes | The source's name as the repository's settings write it, 1 to 120 characters. Unique within a run. at least 1 character; at most 120 characters; pattern `^[^\p{Cc}]+$/u`. | | `sources[].out` | `string` | yes | The diagram's path in the repository, 1 to 300 characters. at least 1 character; at most 300 characters; pattern `^[^\p{Cc}]+$/u`. | | `sources[].status` | `"in-step" \| "drifted" \| "written" \| "failed"` | yes | What the check found: the diagram matches its files (in-step), differs (drifted), was written by the run (written), or could not be made (failed). | | `sources[].added` | `integer` | yes | Items in the files that the diagram does not have. minimum 0; maximum 1000000. | | `sources[].removed` | `integer` | yes | Items in the diagram that the files no longer have. minimum 0; maximum 1000000. | | `sources[].changed` | `integer` | yes | Items both have that differ. minimum 0; maximum 1000000. | | `sources[].errors` | `integer` | yes | Errors the run met making this diagram. minimum 0; maximum 1000000. | | `sources[].owners` | `string[]` | yes | The owners CODEOWNERS gives the diagram's path, as it writes them: at most 20, each @user, @org/team or an email address. | ## Responses | Status | Meaning | Body | |---|---|---| | 201 | Stored. | [`DriftRunReceipt`](https://docs.plotwork.dev/api/schemas.md#driftrunreceipt) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | No key, or the key is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | Not an organisation key (orgs.org_key_required), or the owner's plan does not include the drift dashboard (plan.drift_dashboard_not_included). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the key is another organisation's. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than 1 MiB. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 201 answer has the type [`DriftRunReceipt`](https://docs.plotwork.dev/api/schemas.md#driftrunreceipt). | Field | Type | Required | Description | |---|---|---|---| | `organization` | `object` | yes | The organisation the run was stored for. With `_` in the path, this says which one the key belongs to. | | `organization.id` | `string` | yes | | | `organization.slug` | `string` | yes | | | `repository` | `string` | yes | The repository, as sent. | | `sources` | `integer` | yes | Sources stored. minimum 0. | | `drifted` | `integer` | yes | Sources that drifted. minimum 0. | | `dashboardUrl` | `string` | yes | The organisation's drift dashboard in the portal, for a signed-in member. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Read an organisation's drift dashboard > Page: https://docs.plotwork.dev/api/get-orgs-org-drift. Index of every page: https://docs.plotwork.dev/llms.txt Whether the dashboard is on, the plan and the days runs are kept, every repository with its counts, the latest status of every source with its owners (stale first), and the newest runs. `GET /orgs/{org}/drift` ## TL;DR | Question | Answer | |---|---| | What does it do? | Read an organisation's drift dashboard. | | What does it take? | `org` in the path. | | What does it return? | Status 200 with a body of type [`OrgDrift`](https://docs.plotwork.dev/api/schemas.md#orgdrift). | | What can go wrong? | `401` not signed in, or the key is not valid; `404` no such organisation, the caller is not a member of it, or the key is another organisation's; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Whether the dashboard is on, the plan and the days runs are kept, every repository with its counts, the latest status of every source with its owners (stale first), and the newest runs. Every member may read it, and so may a key of the organisation. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The dashboard. | [`OrgDrift`](https://docs.plotwork.dev/api/schemas.md#orgdrift) | | 401 | Not signed in, or the key is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, the caller is not a member of it, or the key is another organisation's. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`OrgDrift`](https://docs.plotwork.dev/api/schemas.md#orgdrift). | Field | Type | Required | Description | |---|---|---|---| | `active` | `boolean` | yes | True when the owner's plan includes the drift dashboard. When false the lists are empty and run reports are refused. | | `plan` | `"free" \| "pro" \| "enterprise"` | yes | The organisation's plan, which is its owner's. | | `retentionDays` | `integer` | yes | Days a run and a status are kept after they were reported, from the owner's plan. Zero on a plan without the dashboard. minimum 0. | | `repositories` | `DriftRepository[]` | yes | Every repository with a stored status, by name. | | `repositories[].repository` | `string` | yes | | | `repositories[].sources` | `integer` | yes | minimum 0. | | `repositories[].inStep` | `integer` | yes | minimum 0. | | `repositories[].drifted` | `integer` | yes | minimum 0. | | `repositories[].written` | `integer` | yes | minimum 0. | | `repositories[].failed` | `integer` | yes | minimum 0. | | `repositories[].ref` | `string \| null` | yes | | | `repositories[].sha` | `string \| null` | yes | | | `repositories[].runUrl` | `string \| null` | yes | The run on GitHub, or null. | | `repositories[].pullRequestUrl` | `string \| null` | yes | The pull request, or null. | | `repositories[].checkedAt` | `integer` | yes | When the run was reported, in milliseconds since the epoch. | | `sources` | `DriftSource[]` | yes | The latest status of every source: failed first, then drifted, written and in step, each by repository and name. | | `sources[].repository` | `string` | yes | | | `sources[].name` | `string` | yes | | | `sources[].out` | `string` | yes | | | `sources[].status` | `"in-step" \| "drifted" \| "written" \| "failed"` | yes | | | `sources[].added` | `integer` | yes | minimum 0. | | `sources[].removed` | `integer` | yes | minimum 0. | | `sources[].changed` | `integer` | yes | minimum 0. | | `sources[].errors` | `integer` | yes | minimum 0. | | `sources[].owners` | `string[]` | yes | The owners CODEOWNERS gave the path, as written there. | | `sources[].ref` | `string \| null` | yes | | | `sources[].sha` | `string \| null` | yes | | | `sources[].runUrl` | `string \| null` | yes | The run on GitHub, or null. | | `sources[].pullRequestUrl` | `string \| null` | yes | The pull request, or null. | | `sources[].checkedAt` | `integer` | yes | When the run was reported, in milliseconds since the epoch. | | `truncated` | `boolean` | yes | True when there were more sources than one answer carries. | | `runs` | `DriftRun[]` | yes | The latest runs, newest first. | | `runs[].id` | `string` | yes | | | `runs[].repository` | `string` | yes | | | `runs[].event` | `string \| null` | yes | | | `runs[].sources` | `integer` | yes | minimum 0. | | `runs[].drifted` | `integer` | yes | minimum 0. | | `runs[].failed` | `integer` | yes | minimum 0. | | `runs[].errors` | `integer` | yes | minimum 0. | | `runs[].ref` | `string \| null` | yes | | | `runs[].sha` | `string \| null` | yes | | | `runs[].runUrl` | `string \| null` | yes | The run on GitHub, or null. | | `runs[].pullRequestUrl` | `string \| null` | yes | The pull request, or null. | | `runs[].checkedAt` | `integer` | yes | When the run was reported, in milliseconds since the epoch. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Read an organisation's audit log > Page: https://docs.plotwork.dev/api/get-orgs-org-audit. Index of every page: https://docs.plotwork.dev/llms.txt One page of the audit log, newest first: who did what to what, and when. `GET /orgs/{org}/audit` ## TL;DR | Question | Answer | |---|---| | What does it do? | Read an organisation's audit log. | | What does it take? | `org` in the path, and `actor`, `action`, `target`, `from`, `to`, `cursor`, `limit` in the query string. | | What does it return? | Status 200 with a body of type [`OrgAuditPage`](https://docs.plotwork.dev/api/schemas.md#orgauditpage). | | What can go wrong? | `400` a filter or the cursor is not valid; `401` not signed in, or the key is not valid; `403` a member or a reader, or a key without `audit:read` (orgs.forbidden), or a plan that keeps no audit log (plan.audit_not_included); `404` no such organisation, the caller is not a member of it, or the key is another organisation's; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does One page of the audit log, newest first: who did what to what, and when. Filter by actor, action or group, target and time. The owner and admins, or an organisation key with `audit:read`. Pro keeps 30 days, Enterprise 365; Free records nothing. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Query parameters | Field | Type | Required | Description | |---|---|---|---| | `actor` | `string` | no | An actor's account or key id, or their address or key name, matched whole and without regard to case. | | `action` | `string` | no | An action, or a group of them by its first two parts. | | `target` | `string` | no | A target's id, or its name, matched whole and without regard to case. | | `from` | `string` | no | From this time on (inclusive). | | `to` | `string` | no | Before this time (exclusive). | | `cursor` | `string` | no | The page's `nextCursor` from the page before, for the next, older page. | | `limit` | `integer` | no | Events in the page, 1 to 200. Default 50. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The page. | [`OrgAuditPage`](https://docs.plotwork.dev/api/schemas.md#orgauditpage) | | 400 | A filter or the cursor is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in, or the key is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A member or a reader, or a key without `audit:read` (orgs.forbidden), or a plan that keeps no audit log (plan.audit_not_included). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, the caller is not a member of it, or the key is another organisation's. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`OrgAuditPage`](https://docs.plotwork.dev/api/schemas.md#orgauditpage). | Field | Type | Required | Description | |---|---|---|---| | `items` | `OrgAuditEvent[]` | yes | | | `items[].version` | `1` | yes | The version of this shape. A change that is not only an addition gets a new number. | | `items[].id` | `string` | yes | | | `items[].orgId` | `string` | yes | | | `items[].at` | `string` | yes | When it happened: an ISO 8601 time in UTC with milliseconds. | | `items[].action` | `"orgs.org.create" \| "orgs.org.rename" \| "orgs.member.add" \| "orgs.member.remove" \| "orgs.member.leave" \| "orgs.member.role" \| "orgs.owner.transfer" \| "orgs.invite.create" \| "orgs.invite.revoke" \| "orgs.invite.accept" \| "orgs.key.create" \| "orgs.key.reveal" \| "orgs.key.revoke" \| "orgs.diagram.publish" \| "orgs.diagram.update" \| "orgs.diagram.edit" \| "orgs.diagram.restore" \| "orgs.diagram.lock" \| "orgs.diagram.unlock" \| "orgs.diagram.delete" \| "orgs.folder_rule.set" \| "orgs.folder_rule.clear" \| "orgs.brand.set" \| "orgs.brand.clear" \| "orgs.icon.set" \| "orgs.icon.delete" \| "orgs.avatar.set" \| "orgs.avatar.clear" \| "orgs.style_rules.set" \| "orgs.style_rules.clear" \| "orgs.settings.editable" \| "orgs.settings.publishing" \| "orgs.publishing.move" \| "orgs.publishing.unpublish" \| "orgs.drift.report" \| "orgs.drift.settings" \| "orgs.integration.create" \| "orgs.integration.update" \| "orgs.integration.delete" \| "orgs.contract.set" \| "orgs.contract.end" \| "orgs.audit.export" \| "orgs.sso.signin" \| "orgs.sso.config" \| "orgs.sso.domain_add" \| "orgs.sso.domain_verify" \| "orgs.sso.domain_remove" \| "orgs.sso.signin_failed" \| "orgs.sso.jit" \| "orgs.sso.break_glass" \| "orgs.scim.token" \| "orgs.scim.user_create" \| "orgs.scim.user_update" \| "orgs.scim.user_deactivate" \| "orgs.scim.user_delete" \| "orgs.scim.group" \| "orgs.scim.role"` | yes | What happened. Never reused for another meaning. | | `items[].actor` | `object` | yes | | | `items[].actor.kind` | `"user" \| "key" \| "ci" \| "operator"` | yes | | | `items[].actor.id` | `string \| null` | yes | The account id (`user`), the key id (`key`, `ci`), or null (`operator`). | | `items[].actor.name` | `string \| null` | yes | The address or key name when it was recorded, "Plotwork support" for the operator, null once a deleted account is forgotten. | | `items[].target` | `object` | yes | | | `items[].target.type` | `"org" \| "member" \| "invite" \| "key" \| "diagram" \| "folder" \| "brand" \| "icon" \| "avatar" \| "style_rules" \| "settings" \| "drift" \| "contract" \| "sso" \| null` | yes | | | `items[].target.id` | `string \| null` | yes | | | `items[].target.name` | `string \| null` | yes | A member's address, an invited address, a key's, a diagram's or an icon's name, or a folder. | | `items[].metadata` | `object` | yes | A few facts about the event, such as `role` or `keepEditable`. Never a diagram source, a key, a token or a color. | | `items[].ip` | `string \| null` | yes | The network the request came from: the IPv4 /24 or the IPv6 /48, never the full address. | | `items[].userAgent` | `string \| null` | yes | The first 200 characters of the user agent. | | `items[].requestId` | `string \| null` | yes | The request id, which Plotwork support can look up in the API's logs. | | `nextCursor` | `string \| null` | yes | Send as `cursor` for the next, older page; null on the last. | | `retentionDays` | `integer` | yes | How many days the organisation's plan keeps an event. | | `canExport` | `boolean` | yes | Whether the organisation's plan may export the log. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Export an organisation's audit log > Page: https://docs.plotwork.dev/api/get-orgs-org-audit-export. Index of every page: https://docs.plotwork.dev/llms.txt The audit log oldest first, as JSON Lines or CSV, at most 1000 events an export. `GET /orgs/{org}/audit/export` ## TL;DR | Question | Answer | |---|---| | What does it do? | Export an organisation's audit log. | | What does it take? | `org` in the path, and `actor`, `action`, `target`, `from`, `to`, `format`, `after` in the query string. | | What does it return? | Status 200 with no body. | | What can go wrong? | `400` a filter or the cursor is not valid; `401` not signed in, or the key is not valid; `403` a member or a reader, or a key without `audit:read` (orgs.forbidden), or a plan without export (plan.audit_not_included); `404` no such organisation, the caller is not a member of it, or the key is another organisation's; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does The audit log oldest first, as JSON Lines or CSV, at most 1000 events an export. The Plotwork-Next-Cursor header is where the next export starts (send it as after), and Plotwork-More says whether more are waiting. Enterprise only; the owner, admins, or an organisation key with audit:read. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Query parameters | Field | Type | Required | Description | |---|---|---|---| | `actor` | `string` | no | An actor's account or key id, or their address or key name, matched whole and without regard to case. | | `action` | `string` | no | An action, or a group of them by its first two parts. | | `target` | `string` | no | A target's id, or its name, matched whole and without regard to case. | | `from` | `string` | no | From this time on (inclusive). | | `to` | `string` | no | Before this time (exclusive). | | `format` | `"csv" \| "jsonl"` | no | CSV with a header row, or JSON Lines (one event per line, the shape of `plotwork.audit-event.json`). | | `after` | `string` | no | The `Plotwork-Next-Cursor` header of the export before, so a poller takes each event once. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The events, streamed. | none | | 400 | A filter or the cursor is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in, or the key is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A member or a reader, or a key without `audit:read` (orgs.forbidden), or a plan without export (plan.audit_not_included). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, the caller is not a member of it, or the key is another organisation's. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Read an organisation's single sign-on settings > Page: https://docs.plotwork.dev/api/get-orgs-org-sso. Index of every page: https://docs.plotwork.dev/llms.txt The connection to the identity provider (never the client secret), the email domains with the DNS record each needs, the redirect URI to register, and whether the plan has single sign-on. `GET /orgs/{org}/sso` ## TL;DR | Question | Answer | |---|---| | What does it do? | Read an organisation's single sign-on settings. | | What does it take? | `org` in the path. | | What does it return? | Status 200 with a body of type [`SsoSettings`](https://docs.plotwork.dev/api/schemas.md#ssosettings). | | What can go wrong? | `401` not signed in; `403` a member or a reader (orgs.forbidden), a plan without single sign-on (plan.sso_not_included), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` single sign-on is not set up on this server (sso.unavailable), or organisations are not. Every failure uses the one error envelope. | ## What it does The connection to the identity provider (never the client secret), the email domains with the DNS record each needs, the redirect URI to register, and whether the plan has single sign-on. The owner and admins only. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The settings. | [`SsoSettings`](https://docs.plotwork.dev/api/schemas.md#ssosettings) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A member or a reader (orgs.forbidden), a plan without single sign-on (plan.sso_not_included), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Single sign-on is not set up on this server (sso.unavailable), or organisations are not. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`SsoSettings`](https://docs.plotwork.dev/api/schemas.md#ssosettings). | Field | Type | Required | Description | |---|---|---|---| | `available` | `boolean` | yes | Whether the organisation's plan has single sign-on (Enterprise). | | `plan` | `"free" \| "pro" \| "enterprise"` | yes | | | `redirectUri` | `string` | yes | The redirect URI to register at the identity provider. | | `saml` | `SamlServiceProvider` | yes | | | `saml.entityId` | `string` | yes | Our entity id for this organisation: the audience to set at the provider. | | `saml.acsUrl` | `string` | yes | Our assertion consumer service for this organisation: the single sign-on URL to set at the provider. | | `saml.metadataUrl` | `string` | yes | Our service provider metadata, for a provider that imports it. | | `samlAvailable` | `boolean` | yes | Whether this server can check SAML responses. | | `connection` | `SsoConnection` | yes | | | `connection.protocol` | `"oidc" \| "saml"` | yes | OpenID Connect or SAML 2.0. One connection, one protocol. | | `connection.issuer` | `string` | yes | The OpenID Connect issuer, or the SAML provider's entity id. | | `connection.clientId` | `string \| null` | yes | OpenID Connect only. | | `connection.secretSet` | `boolean` | yes | True when an OpenID Connect client secret is stored. It is never returned. Always false for SAML. | | `connection.tokenAuthMethod` | `"client_secret_basic" \| "client_secret_post" \| null` | yes | | | `connection.saml` | `SamlConnection` | yes | SAML only. | | `connection.saml.entityId` | `string` | yes | The provider's entity id, which every `Issuer` must equal. | | `connection.saml.ssoUrl` | `string` | yes | The provider's sign-in service on the redirect binding. | | `connection.saml.certificates` | `object[]` | yes | | | `connection.saml.emailAttribute` | `string \| null` | yes | The attribute that carries the address, or null when the name identifier does. | | `connection.saml.metadataUrl` | `string \| null` | yes | Where the metadata was fetched from, or null when it was pasted. | | `connection.enabled` | `boolean` | yes | | | `connection.requireSso` | `boolean` | yes | | | `connection.jit` | `boolean` | yes | | | `connection.jitRole` | `"member" \| "reader"` | yes | | | `connection.updatedAt` | `integer` | yes | | | `domains` | `SsoDomain[]` | yes | | | `domains[].id` | `string` | yes | | | `domains[].domain` | `string` | yes | | | `domains[].verified` | `boolean` | yes | | | `domains[].verifiedAt` | `integer \| null` | yes | | | `domains[].checkedAt` | `integer \| null` | yes | | | `domains[].createdAt` | `integer` | yes | | | `domains[].record` | `object` | yes | | | `domains[].record.name` | `string` | yes | | | `domains[].record.type` | `"TXT"` | yes | | | `domains[].record.value` | `string` | yes | | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Set up an OpenID Connect identity provider > Page: https://docs.plotwork.dev/api/put-orgs-org-sso-connection. Index of every page: https://docs.plotwork.dev/llms.txt Stores the issuer, the client id and the client secret after checking the issuer's discovery document. `PUT /orgs/{org}/sso/connection` ## TL;DR | Question | Answer | |---|---| | What does it do? | Set up an OpenID Connect identity provider. | | What does it take? | A JSON body of type [`SsoConnectionBody`](https://docs.plotwork.dev/api/schemas.md#ssoconnectionbody). | | What does it return? | Status 200 with a body of type [`SsoConnection`](https://docs.plotwork.dev/api/schemas.md#ssoconnection). | | What can go wrong? | `400` the body is not valid, or the secret is missing the first time (sso.invalid); `401` not signed in; `403` a member or a reader (orgs.forbidden), a plan without single sign-on (plan.sso_not_included), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `422` the issuer's discovery document cannot be used (sso.discovery_failed); `429` too many requests; `503` single sign-on is not set up on this server (sso.unavailable), or organisations are not. Every failure uses the one error envelope. | ## What it does Stores the issuer, the client id and the client secret after checking the issuer's discovery document. The secret is required the first time, sealed, and never returned. A new connection is off until it is turned on. Enterprise; the owner and admins. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Request body The body has the type [`SsoConnectionBody`](https://docs.plotwork.dev/api/schemas.md#ssoconnectionbody). | Field | Type | Required | Description | |---|---|---|---| | `issuer` | `string` | yes | The issuer URL of your own tenant. at least 9 characters; at most 300 characters. | | `clientId` | `string` | yes | at least 1 character; at most 255 characters. | | `clientSecret` | `string` | no | Required the first time. Left out later, the stored one is kept. Never returned. at least 1 character; at most 1000 characters. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The connection. | [`SsoConnection`](https://docs.plotwork.dev/api/schemas.md#ssoconnection) | | 400 | The body is not valid, or the secret is missing the first time (sso.invalid). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A member or a reader (orgs.forbidden), a plan without single sign-on (plan.sso_not_included), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 422 | The issuer's discovery document cannot be used (sso.discovery_failed). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Single sign-on is not set up on this server (sso.unavailable), or organisations are not. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`SsoConnection`](https://docs.plotwork.dev/api/schemas.md#ssoconnection). | Field | Type | Required | Description | |---|---|---|---| | `protocol` | `"oidc" \| "saml"` | yes | OpenID Connect or SAML 2.0. One connection, one protocol. | | `issuer` | `string` | yes | The OpenID Connect issuer, or the SAML provider's entity id. | | `clientId` | `string \| null` | yes | OpenID Connect only. | | `secretSet` | `boolean` | yes | True when an OpenID Connect client secret is stored. It is never returned. Always false for SAML. | | `tokenAuthMethod` | `"client_secret_basic" \| "client_secret_post" \| null` | yes | | | `saml` | `SamlConnection` | yes | SAML only. | | `saml.entityId` | `string` | yes | The provider's entity id, which every `Issuer` must equal. | | `saml.ssoUrl` | `string` | yes | The provider's sign-in service on the redirect binding. | | `saml.certificates` | `object[]` | yes | | | `saml.certificates[].fingerprint` | `string` | yes | SHA-256 of the certificate, as providers show it. | | `saml.certificates[].notAfter` | `integer` | yes | When the certificate expires, in milliseconds since the epoch. Shown, not enforced. | | `saml.emailAttribute` | `string \| null` | yes | The attribute that carries the address, or null when the name identifier does. | | `saml.metadataUrl` | `string \| null` | yes | Where the metadata was fetched from, or null when it was pasted. | | `enabled` | `boolean` | yes | | | `requireSso` | `boolean` | yes | | | `jit` | `boolean` | yes | | | `jitRole` | `"member" \| "reader"` | yes | | | `updatedAt` | `integer` | yes | | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Turn single sign-on on or off, and set Require SSO and who joins on first sign-in > Page: https://docs.plotwork.dev/api/patch-orgs-org-sso-connection. Index of every page: https://docs.plotwork.dev/llms.txt Each field left out keeps its value. `PATCH /orgs/{org}/sso/connection` ## TL;DR | Question | Answer | |---|---| | What does it do? | Turn single sign-on on or off, and set Require SSO and who joins on first sign-in. | | What does it take? | A JSON body of type [`SsoSwitchesBody`](https://docs.plotwork.dev/api/schemas.md#ssoswitchesbody). | | What does it return? | Status 200 with a body of type [`SsoConnection`](https://docs.plotwork.dev/api/schemas.md#ssoconnection). | | What can go wrong? | `400` the body is not valid, no connection is set up, or require sso cannot be turned on yet (sso.invalid); `401` not signed in; `403` a member or a reader (orgs.forbidden), a plan without single sign-on (plan.sso_not_included), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` single sign-on is not set up on this server (sso.unavailable), or organisations are not. Every failure uses the one error envelope. | ## What it does Each field left out keeps its value. Require SSO needs the connection on and a verified domain; turning the connection off turns Require SSO off. People who join on first sign-in are members or readers, never admins, within the member limit. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Request body The body has the type [`SsoSwitchesBody`](https://docs.plotwork.dev/api/schemas.md#ssoswitchesbody). | Field | Type | Required | Description | |---|---|---|---| | `enabled` | `boolean` | no | | | `requireSso` | `boolean` | no | | | `jit` | `boolean` | no | | | `jitRole` | `"member" \| "reader"` | no | | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The connection. | [`SsoConnection`](https://docs.plotwork.dev/api/schemas.md#ssoconnection) | | 400 | The body is not valid, no connection is set up, or Require SSO cannot be turned on yet (sso.invalid). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A member or a reader (orgs.forbidden), a plan without single sign-on (plan.sso_not_included), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Single sign-on is not set up on this server (sso.unavailable), or organisations are not. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`SsoConnection`](https://docs.plotwork.dev/api/schemas.md#ssoconnection). | Field | Type | Required | Description | |---|---|---|---| | `protocol` | `"oidc" \| "saml"` | yes | OpenID Connect or SAML 2.0. One connection, one protocol. | | `issuer` | `string` | yes | The OpenID Connect issuer, or the SAML provider's entity id. | | `clientId` | `string \| null` | yes | OpenID Connect only. | | `secretSet` | `boolean` | yes | True when an OpenID Connect client secret is stored. It is never returned. Always false for SAML. | | `tokenAuthMethod` | `"client_secret_basic" \| "client_secret_post" \| null` | yes | | | `saml` | `SamlConnection` | yes | SAML only. | | `saml.entityId` | `string` | yes | The provider's entity id, which every `Issuer` must equal. | | `saml.ssoUrl` | `string` | yes | The provider's sign-in service on the redirect binding. | | `saml.certificates` | `object[]` | yes | | | `saml.certificates[].fingerprint` | `string` | yes | SHA-256 of the certificate, as providers show it. | | `saml.certificates[].notAfter` | `integer` | yes | When the certificate expires, in milliseconds since the epoch. Shown, not enforced. | | `saml.emailAttribute` | `string \| null` | yes | The attribute that carries the address, or null when the name identifier does. | | `saml.metadataUrl` | `string \| null` | yes | Where the metadata was fetched from, or null when it was pasted. | | `enabled` | `boolean` | yes | | | `requireSso` | `boolean` | yes | | | `jit` | `boolean` | yes | | | `jitRole` | `"member" \| "reader"` | yes | | | `updatedAt` | `integer` | yes | | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Remove the identity provider > Page: https://docs.plotwork.dev/api/delete-orgs-org-sso-connection. Index of every page: https://docs.plotwork.dev/llms.txt Removes the connection and its secret; sign-in through the provider stops. `DELETE /orgs/{org}/sso/connection` ## TL;DR | Question | Answer | |---|---| | What does it do? | Remove the identity provider. | | What does it take? | `org` in the path. | | What does it return? | Status 204 with no body. | | What can go wrong? | `401` not signed in; `403` a member or a reader (orgs.forbidden), a plan without single sign-on (plan.sso_not_included), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` single sign-on is not set up on this server (sso.unavailable), or organisations are not. Every failure uses the one error envelope. | ## What it does Removes the connection and its secret; sign-in through the provider stops. The domains and the linked people stay. The owner and admins. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Removed. | none | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A member or a reader (orgs.forbidden), a plan without single sign-on (plan.sso_not_included), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Single sign-on is not set up on this server (sso.unavailable), or organisations are not. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Set up a SAML identity provider > Page: https://docs.plotwork.dev/api/put-orgs-org-sso-saml. Index of every page: https://docs.plotwork.dev/llms.txt Reads the provider's metadata from its HTTPS address or pasted XML and stores the entity id, the sign-in service and the signing certificates. `PUT /orgs/{org}/sso/saml` ## TL;DR | Question | Answer | |---|---| | What does it do? | Set up a SAML identity provider. | | What does it take? | A JSON body of type [`SamlConnectionBody`](https://docs.plotwork.dev/api/schemas.md#samlconnectionbody). | | What does it return? | Status 200 with a body of type [`SsoConnection`](https://docs.plotwork.dev/api/schemas.md#ssoconnection). | | What can go wrong? | `400` the body is not valid, or no metadata the first time (sso.invalid); `401` not signed in; `403` a member or a reader (orgs.forbidden), a plan without single sign-on (plan.sso_not_included), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `422` the metadata cannot be fetched or used (sso.metadata_invalid); `429` too many requests; `503` single sign-on is not set up on this server (sso.unavailable), or organisations are not. Every failure uses the one error envelope. | ## What it does Reads the provider's metadata from its HTTPS address or pasted XML and stores the entity id, the sign-in service and the signing certificates. `emailAttribute` names the attribute with the address; null reads the name identifier. A new connection is off until it is turned on. Enterprise; the owner and admins. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Request body The body has the type [`SamlConnectionBody`](https://docs.plotwork.dev/api/schemas.md#samlconnectionbody). | Field | Type | Required | Description | |---|---|---|---| | `metadataUrl` | `string` | no | The address of the provider's metadata, fetched over HTTPS. Give this or `metadataXml`, or neither to keep the stored metadata. at least 9 characters; at most 500 characters. | | `metadataXml` | `string` | no | The provider's metadata XML, pasted. at least 1 character; at most 262144 characters. | | `emailAttribute` | `string \| null` | no | The attribute that carries the address. Null reads the name identifier; left out keeps the stored choice. at least 1 character; at most 200 characters. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The connection. | [`SsoConnection`](https://docs.plotwork.dev/api/schemas.md#ssoconnection) | | 400 | The body is not valid, or no metadata the first time (sso.invalid). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A member or a reader (orgs.forbidden), a plan without single sign-on (plan.sso_not_included), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 422 | The metadata cannot be fetched or used (sso.metadata_invalid). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Single sign-on is not set up on this server (sso.unavailable), or organisations are not. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`SsoConnection`](https://docs.plotwork.dev/api/schemas.md#ssoconnection). | Field | Type | Required | Description | |---|---|---|---| | `protocol` | `"oidc" \| "saml"` | yes | OpenID Connect or SAML 2.0. One connection, one protocol. | | `issuer` | `string` | yes | The OpenID Connect issuer, or the SAML provider's entity id. | | `clientId` | `string \| null` | yes | OpenID Connect only. | | `secretSet` | `boolean` | yes | True when an OpenID Connect client secret is stored. It is never returned. Always false for SAML. | | `tokenAuthMethod` | `"client_secret_basic" \| "client_secret_post" \| null` | yes | | | `saml` | `SamlConnection` | yes | SAML only. | | `saml.entityId` | `string` | yes | The provider's entity id, which every `Issuer` must equal. | | `saml.ssoUrl` | `string` | yes | The provider's sign-in service on the redirect binding. | | `saml.certificates` | `object[]` | yes | | | `saml.certificates[].fingerprint` | `string` | yes | SHA-256 of the certificate, as providers show it. | | `saml.certificates[].notAfter` | `integer` | yes | When the certificate expires, in milliseconds since the epoch. Shown, not enforced. | | `saml.emailAttribute` | `string \| null` | yes | The attribute that carries the address, or null when the name identifier does. | | `saml.metadataUrl` | `string \| null` | yes | Where the metadata was fetched from, or null when it was pasted. | | `enabled` | `boolean` | yes | | | `requireSso` | `boolean` | yes | | | `jit` | `boolean` | yes | | | `jitRole` | `"member" \| "reader"` | yes | | | `updatedAt` | `integer` | yes | | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Add an email domain > Page: https://docs.plotwork.dev/api/post-orgs-org-sso-domains. Index of every page: https://docs.plotwork.dev/llms.txt Adds a domain and answers the TXT record to publish at `_plotwork-verify.<domain>`. `POST /orgs/{org}/sso/domains` ## TL;DR | Question | Answer | |---|---| | What does it do? | Add an email domain. | | What does it take? | A JSON body of type [`SsoDomainBody`](https://docs.plotwork.dev/api/schemas.md#ssodomainbody). | | What does it return? | Status 201 with a body of type [`SsoDomain`](https://docs.plotwork.dev/api/schemas.md#ssodomain). | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` a member or a reader (orgs.forbidden), a plan without single sign-on (plan.sso_not_included), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `409` another organisation verified this domain (sso.domain_taken); `422` not a domain a provider can be proved for (sso.domain_invalid); `429` too many requests; `503` single sign-on is not set up on this server (sso.unavailable), or organisations are not. Every failure uses the one error envelope. | ## What it does Adds a domain and answers the TXT record to publish at `_plotwork-verify.<domain>`. It counts once verified. Public mail domains are refused; at most 20; one another organisation verified is 409 sso.domain_taken. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Request body The body has the type [`SsoDomainBody`](https://docs.plotwork.dev/api/schemas.md#ssodomainbody). | Field | Type | Required | Description | |---|---|---|---| | `domain` | `string` | yes | at least 3 characters; at most 253 characters. | ## Responses | Status | Meaning | Body | |---|---|---| | 201 | The domain and its record. | [`SsoDomain`](https://docs.plotwork.dev/api/schemas.md#ssodomain) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A member or a reader (orgs.forbidden), a plan without single sign-on (plan.sso_not_included), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | Another organisation verified this domain (sso.domain_taken). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 422 | Not a domain a provider can be proved for (sso.domain_invalid). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Single sign-on is not set up on this server (sso.unavailable), or organisations are not. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 201 answer has the type [`SsoDomain`](https://docs.plotwork.dev/api/schemas.md#ssodomain). | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `domain` | `string` | yes | | | `verified` | `boolean` | yes | | | `verifiedAt` | `integer \| null` | yes | | | `checkedAt` | `integer \| null` | yes | | | `createdAt` | `integer` | yes | | | `record` | `object` | yes | | | `record.name` | `string` | yes | | | `record.type` | `"TXT"` | yes | | | `record.value` | `string` | yes | | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Check a domain's DNS record > Page: https://docs.plotwork.dev/api/post-orgs-org-sso-domains-domain-verify. Index of every page: https://docs.plotwork.dev/llms.txt Looks up the TXT record through DNS over HTTPS and marks the domain verified when it matches. `POST /orgs/{org}/sso/domains/{domain}/verify` ## TL;DR | Question | Answer | |---|---| | What does it do? | Check a domain's DNS record. | | What does it take? | `org`, `domain` in the path. | | What does it return? | Status 200 with a body of type [`SsoDomainVerify`](https://docs.plotwork.dev/api/schemas.md#ssodomainverify). | | What can go wrong? | `401` not signed in; `403` a member or a reader (orgs.forbidden), a plan without single sign-on (plan.sso_not_included), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `409` another organisation verified this domain (sso.domain_taken); `429` too many requests; `503` single sign-on is not set up on this server (sso.unavailable), or organisations are not. Every failure uses the one error envelope. | ## What it does Looks up the TXT record through DNS over HTTPS and marks the domain verified when it matches. `checked` is false when the resolver did not answer. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | | `domain` | `string` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The domain as it is now. | [`SsoDomainVerify`](https://docs.plotwork.dev/api/schemas.md#ssodomainverify) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A member or a reader (orgs.forbidden), a plan without single sign-on (plan.sso_not_included), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | Another organisation verified this domain (sso.domain_taken). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Single sign-on is not set up on this server (sso.unavailable), or organisations are not. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`SsoDomainVerify`](https://docs.plotwork.dev/api/schemas.md#ssodomainverify). | Field | Type | Required | Description | |---|---|---|---| | `domain` | `SsoDomain` | yes | | | `domain.id` | `string` | yes | | | `domain.domain` | `string` | yes | | | `domain.verified` | `boolean` | yes | | | `domain.verifiedAt` | `integer \| null` | yes | | | `domain.checkedAt` | `integer \| null` | yes | | | `domain.createdAt` | `integer` | yes | | | `domain.record` | `object` | yes | | | `domain.record.name` | `string` | yes | | | `domain.record.type` | `"TXT"` | yes | | | `domain.record.value` | `string` | yes | | | `checked` | `boolean` | yes | False when the resolver did not answer, so nothing was learned. Try again. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Remove an email domain > Page: https://docs.plotwork.dev/api/delete-orgs-org-sso-domains-domain. Index of every page: https://docs.plotwork.dev/llms.txt Removes the domain. `DELETE /orgs/{org}/sso/domains/{domain}` ## TL;DR | Question | Answer | |---|---| | What does it do? | Remove an email domain. | | What does it take? | `org`, `domain` in the path. | | What does it return? | Status 204 with no body. | | What can go wrong? | `401` not signed in; `403` a member or a reader (orgs.forbidden), a plan without single sign-on (plan.sso_not_included), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` single sign-on is not set up on this server (sso.unavailable), or organisations are not. Every failure uses the one error envelope. | ## What it does Removes the domain. Removing the last verified domain turns Require SSO off. The owner and admins. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | | `domain` | `string` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Removed. | none | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A member or a reader (orgs.forbidden), a plan without single sign-on (plan.sso_not_included), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Single sign-on is not set up on this server (sso.unavailable), or organisations are not. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Read an organisation's SCIM settings > Page: https://docs.plotwork.dev/api/get-orgs-org-scim. Index of every page: https://docs.plotwork.dev/llms.txt The SCIM base URL, the live token without its value, how many people the provider manages, its groups and their roles, and whether the plan includes SCIM. `GET /orgs/{org}/scim` ## TL;DR | Question | Answer | |---|---| | What does it do? | Read an organisation's SCIM settings. | | What does it take? | `org` in the path. | | What does it return? | Status 200 with a body of type [`ScimSettings`](https://docs.plotwork.dev/api/schemas.md#scimsettings). | | What can go wrong? | `401` not signed in; `403` a member or a reader (orgs.forbidden), a plan without scim (plan.sso_not_included), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does The SCIM base URL, the live token without its value, how many people the provider manages, its groups and their roles, and whether the plan includes SCIM. The owner and admins. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The settings. | [`ScimSettings`](https://docs.plotwork.dev/api/schemas.md#scimsettings) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A member or a reader (orgs.forbidden), a plan without SCIM (plan.sso_not_included), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`ScimSettings`](https://docs.plotwork.dev/api/schemas.md#scimsettings). | Field | Type | Required | Description | |---|---|---|---| | `available` | `boolean` | yes | Whether the organisation's plan includes SCIM (Enterprise). | | `baseUrl` | `string` | yes | The SCIM base URL to give the identity provider. | | `token` | `ScimTokenView` | yes | | | `token.prefix` | `string` | yes | The first characters of the token, to tell it apart. | | `token.createdAt` | `integer` | yes | | | `token.lastUsedAt` | `integer \| null` | yes | When the provider last used it, to the minute. Null until it has. | | `users` | `integer` | yes | How many people the provider manages here, active or not. | | `groups` | `ScimGroupView[]` | yes | | | `groups[].id` | `string` | yes | | | `groups[].displayName` | `string` | yes | | | `groups[].role` | `"admin" \| "member" \| "reader" \| null` | yes | The role the group gives its members, or null when it gives none. | | `groups[].members` | `integer` | yes | | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Make or rotate the SCIM token > Page: https://docs.plotwork.dev/api/post-orgs-org-scim-token. Index of every page: https://docs.plotwork.dev/llms.txt Makes a bearer token for the identity provider, shown once and stored only as a hash. `POST /orgs/{org}/scim/token` ## TL;DR | Question | Answer | |---|---| | What does it do? | Make or rotate the SCIM token. | | What does it take? | `org` in the path. | | What does it return? | Status 201 with a body of type [`ScimTokenIssued`](https://docs.plotwork.dev/api/schemas.md#scimtokenissued). | | What can go wrong? | `401` not signed in; `403` a member or a reader (orgs.forbidden), a plan without scim (plan.sso_not_included), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does Makes a bearer token for the identity provider, shown once and stored only as a hash. A live token is revoked at the same moment. Enterprise; the owner and admins. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Responses | Status | Meaning | Body | |---|---|---| | 201 | The token, shown once. | [`ScimTokenIssued`](https://docs.plotwork.dev/api/schemas.md#scimtokenissued) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A member or a reader (orgs.forbidden), a plan without SCIM (plan.sso_not_included), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 201 answer has the type [`ScimTokenIssued`](https://docs.plotwork.dev/api/schemas.md#scimtokenissued). | Field | Type | Required | Description | |---|---|---|---| | `token` | `string` | yes | The bearer token for the identity provider. Shown once; only its hash is kept. | | `view` | `ScimTokenView` | yes | | | `view.prefix` | `string` | yes | The first characters of the token, to tell it apart. | | `view.createdAt` | `integer` | yes | | | `view.lastUsedAt` | `integer \| null` | yes | When the provider last used it, to the minute. Null until it has. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Revoke the SCIM token > Page: https://docs.plotwork.dev/api/delete-orgs-org-scim-token. Index of every page: https://docs.plotwork.dev/llms.txt The provider's next SCIM call is refused. `DELETE /orgs/{org}/scim/token` ## TL;DR | Question | Answer | |---|---| | What does it do? | Revoke the SCIM token. | | What does it take? | `org` in the path. | | What does it return? | Status 204 with no body. | | What can go wrong? | `401` not signed in; `403` a member or a reader (orgs.forbidden), a plan without scim (plan.sso_not_included), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does The provider's next SCIM call is refused. People it provisioned stay as they are. The owner and admins. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Responses | Status | Meaning | Body | |---|---|---| | 204 | Revoked. | none | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A member or a reader (orgs.forbidden), a plan without SCIM (plan.sso_not_included), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Choose the role a SCIM group gives > Page: https://docs.plotwork.dev/api/patch-orgs-org-scim-groups-group. Index of every page: https://docs.plotwork.dev/llms.txt admin, member, reader, or null. `PATCH /orgs/{org}/scim/groups/{group}` ## TL;DR | Question | Answer | |---|---| | What does it do? | Choose the role a SCIM group gives. | | What does it take? | A JSON body of type [`ScimGroupRoleBody`](https://docs.plotwork.dev/api/schemas.md#scimgrouprolebody). | | What does it return? | Status 200 with a body of type [`ScimGroupView`](https://docs.plotwork.dev/api/schemas.md#scimgroupview). | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` a member or a reader (orgs.forbidden), a plan without scim (plan.sso_not_included), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does admin, member, reader, or null. Once any group gives a role, provisioned people's roles follow their groups. The owner is never changed. Enterprise; the owner and admins. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | | `group` | `string` | yes | | ## Request body The body has the type [`ScimGroupRoleBody`](https://docs.plotwork.dev/api/schemas.md#scimgrouprolebody). | Field | Type | Required | Description | |---|---|---|---| | `role` | `"admin" \| "member" \| "reader" \| null` | yes | | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The group. | [`ScimGroupView`](https://docs.plotwork.dev/api/schemas.md#scimgroupview) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A member or a reader (orgs.forbidden), a plan without SCIM (plan.sso_not_included), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`ScimGroupView`](https://docs.plotwork.dev/api/schemas.md#scimgroupview). | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | | | `displayName` | `string` | yes | | | `role` | `"admin" \| "member" \| "reader" \| null` | yes | The role the group gives its members, or null when it gives none. | | `members` | `integer` | yes | | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Read what priority support is for an organisation > Page: https://docs.plotwork.dev/api/get-orgs-org-support. Index of every page: https://docs.plotwork.dev/llms.txt The named contact, the first response target, the support hours and whether the plan has an uptime SLA, for every member; the contract dates for the owner and admins, never its price. `GET /orgs/{org}/support` ## TL;DR | Question | Answer | |---|---| | What does it do? | Read what priority support is for an organisation. | | What does it take? | `org` in the path. | | What does it return? | Status 200 with a body of type [`SupportSettings`](https://docs.plotwork.dev/api/schemas.md#supportsettings). | | What can go wrong? | `401` not signed in; `404` no such organisation, or the caller is not a member of it; `429` too many requests; `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does The named contact, the first response target, the support hours and whether the plan has an uptime SLA, for every member; the contract dates for the owner and admins, never its price. `available` is false on a plan without priority support. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | What support is for the organisation. | [`SupportSettings`](https://docs.plotwork.dev/api/schemas.md#supportsettings) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`SupportSettings`](https://docs.plotwork.dev/api/schemas.md#supportsettings). | Field | Type | Required | Description | |---|---|---|---| | `available` | `boolean` | yes | True when the organisation's plan includes priority support (Enterprise). | | `plan` | `string` | yes | The organisation's plan. | | `contact` | `object \| null` | yes | The named contact, or null when none is named yet. A request still reaches Plotwork support. | | `contact.name` | `string` | yes | | | `contact.email` | `string` | yes | | | `firstResponseHours` | `integer \| null` | yes | The first response target, in business hours, or null when the contract names none. | | `hours` | `string \| null` | yes | When support is staffed, as the contract says it, or null. | | `uptimeSla` | `boolean` | yes | True when the plan includes an uptime SLA. | | `contract` | `object \| null` | yes | The dates of the contract: for the owner and the admins only, and null for everyone else or a contract set without dates. | | `contract.startsOn` | `string` | yes | The first day of the contract, `YYYY-MM-DD` (UTC). | | `contract.endsOn` | `string` | yes | The last day of the contract, `YYYY-MM-DD` (UTC). | | `contract.autoRenew` | `boolean` | yes | Whether the contract renews by itself at the end. | | `contract.noticeDays` | `integer` | yes | The notice either side gives before the end, in days. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Ask the organisation's named contact for help > Page: https://docs.plotwork.dev/api/post-orgs-org-support-requests. Index of every page: https://docs.plotwork.dev/llms.txt A subject, a message and a severity, stored for 365 days and emailed to the named contact with the sender as the reply-to. `POST /orgs/{org}/support/requests` ## TL;DR | Question | Answer | |---|---| | What does it do? | Ask the organisation's named contact for help. | | What does it take? | A JSON body of type [`SupportRequestBody`](https://docs.plotwork.dev/api/schemas.md#supportrequestbody). | | What does it return? | Status 201 with a body of type [`SupportAck`](https://docs.plotwork.dev/api/schemas.md#supportack). | | What can go wrong? | `400` a field is missing, too long, or not one of the choices; `401` not signed in; `403` a plan without priority support (plan.support_not_included), or the request came from an origin that may not make changes; `404` no such organisation, or the caller is not a member of it; `413` the body is larger than 32 kib; `429` too many requests, or the organisation's requests for the day are used (support.daily_limit); `503` organisations are not set up on this server. Every failure uses the one error envelope. | ## What it does A subject, a message and a severity, stored for 365 days and emailed to the named contact with the sender as the reply-to. Any member of an Enterprise organisation; at most 20 an organisation a day. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `org` | `string` | yes | The organisation's id or its slug. | ## Request body The body has the type [`SupportRequestBody`](https://docs.plotwork.dev/api/schemas.md#supportrequestbody). | Field | Type | Required | Description | |---|---|---|---| | `subject` | `string` | yes | One line on what it is about. 1 to 160 characters. at least 1 character; at most 160 characters. | | `message` | `string` | yes | What happened, what was expected, and anything that helps reproduce it. 1 to 5000 characters; line breaks are kept. at least 1 character; at most 5000 characters. | | `severity` | `"question" \| "broken" \| "urgent"` | yes | `question` for a question, `broken` when something does not work, `urgent` when production is affected. The contact reads the urgent ones first. | ## Responses | Status | Meaning | Body | |---|---|---| | 201 | Sent. | [`SupportAck`](https://docs.plotwork.dev/api/schemas.md#supportack) | | 400 | A field is missing, too long, or not one of the choices. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | A plan without priority support (plan.support_not_included), or the request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such organisation, or the caller is not a member of it. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than 32 KiB. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests, or the organisation's requests for the day are used (support.daily_limit). | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Organisations are not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 201 answer has the type [`SupportAck`](https://docs.plotwork.dev/api/schemas.md#supportack). | Field | Type | Required | Description | |---|---|---|---| | `received` | `true` | yes | | | `contactName` | `string \| null` | yes | The name of the contact who will answer, or null when none is named. | | `firstResponseHours` | `integer \| null` | yes | The first response target, in business hours, or null. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Read the account's plan > Page: https://docs.plotwork.dev/api/get-user-billing. Index of every page: https://docs.plotwork.dev/llms.txt The plan the account gets right now, the status of its subscription, when the period paid for ends, whether it is set to end then, and whether the account has billing details at Stripe. `GET /user/billing` ## TL;DR | Question | Answer | |---|---| | What does it do? | Read the account's plan. | | What does it take? | Nothing. | | What does it return? | Status 200 with a body of type [`BillingState`](https://docs.plotwork.dev/api/schemas.md#billingstate). | | What can go wrong? | `401` not signed in; `429` too many requests. Every failure uses the one error envelope. | ## What it does The plan the account gets right now, the status of its subscription, when the period paid for ends, whether it is set to end then, and whether the account has billing details at Stripe. Reads the account's own record and does not call Stripe. ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The account's plan. | [`BillingState`](https://docs.plotwork.dev/api/schemas.md#billingstate) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`BillingState`](https://docs.plotwork.dev/api/schemas.md#billingstate). | Field | Type | Required | Description | |---|---|---|---| | `plan` | `"free" \| "pro"` | yes | The plan the account gets right now. It is the free plan whenever the subscription is not paying, whatever plan was bought. | | `status` | `string` | yes | Stripe's status for the subscription (`active`, `trialing`, `past_due`, `canceled`, `unpaid`, `incomplete`, and so on), or `none` when the account never started one. | | `currentPeriodEnd` | `integer \| null` | yes | When the period paid for ends, in milliseconds since the epoch, or null when there is no subscription. | | `cancelAtPeriodEnd` | `boolean` | yes | True while the subscription is scheduled to end at the close of the period already paid for. | | `hasCustomer` | `boolean` | yes | True once the account has billing details at Stripe, which is when the billing portal can be opened. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Start a checkout for a paid plan > Page: https://docs.plotwork.dev/api/post-user-billing-checkout. Index of every page: https://docs.plotwork.dev/llms.txt Makes a Stripe Checkout Session for the plan and returns the address of Stripe's hosted page. `POST /user/billing/checkout` ## TL;DR | Question | Answer | |---|---| | What does it do? | Start a checkout for a paid plan. | | What does it take? | A JSON body of type [`CheckoutBody`](https://docs.plotwork.dev/api/schemas.md#checkoutbody). | | What does it return? | Status 200 with a body of type [`BillingUrl`](https://docs.plotwork.dev/api/schemas.md#billingurl). | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` the request came from an origin that may not make changes; `409` the account is already on a paid plan; `429` too many requests; `502` stripe could not complete the request; `503` billing is not set up on this server. Every failure uses the one error envelope. | ## What it does Makes a Stripe Checkout Session for the plan and returns the address of Stripe's hosted page. An account already on a paid plan is refused with 409; the billing portal changes or ends the plan it has. ## Request body The body has the type [`CheckoutBody`](https://docs.plotwork.dev/api/schemas.md#checkoutbody). | Field | Type | Required | Description | |---|---|---|---| | `plan` | `"pro"` | yes | The plan to subscribe to. `pro` is the only paid plan. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The address of the Stripe page. | [`BillingUrl`](https://docs.plotwork.dev/api/schemas.md#billingurl) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | The account is already on a paid plan. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 502 | Stripe could not complete the request. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Billing is not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`BillingUrl`](https://docs.plotwork.dev/api/schemas.md#billingurl). | Field | Type | Required | Description | |---|---|---|---| | `url` | `string` | yes | An address on Stripe's own site. Send the browser there; it expires, so do not store it. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Read a finished checkout back > Page: https://docs.plotwork.dev/api/post-user-billing-verify-checkout. Index of every page: https://docs.plotwork.dev/llms.txt Asks Stripe for the checkout session and its subscription and stores the same state the webhook stores. `POST /user/billing/verify-checkout` ## TL;DR | Question | Answer | |---|---| | What does it do? | Read a finished checkout back. | | What does it take? | A JSON body of type [`VerifyCheckoutBody`](https://docs.plotwork.dev/api/schemas.md#verifycheckoutbody). | | What does it return? | Status 200 with a body of type [`BillingState`](https://docs.plotwork.dev/api/schemas.md#billingstate). | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` the request came from an origin that may not make changes; `404` no such checkout session for this account; `429` too many requests; `502` stripe could not complete the request; `503` billing is not set up on this server. Every failure uses the one error envelope. | ## What it does Asks Stripe for the checkout session and its subscription and stores the same state the webhook stores. A session that is not this account's, or does not exist, is a 404 either way. ## Request body The body has the type [`VerifyCheckoutBody`](https://docs.plotwork.dev/api/schemas.md#verifycheckoutbody). | Field | Type | Required | Description | |---|---|---|---| | `sessionId` | `string` | yes | The Checkout Session id Stripe put in the return address. pattern `^cs_[A-Za-z0-9_]{8,200}$`. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The account's plan after the check. | [`BillingState`](https://docs.plotwork.dev/api/schemas.md#billingstate) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such checkout session for this account. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 502 | Stripe could not complete the request. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Billing is not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`BillingState`](https://docs.plotwork.dev/api/schemas.md#billingstate). | Field | Type | Required | Description | |---|---|---|---| | `plan` | `"free" \| "pro"` | yes | The plan the account gets right now. It is the free plan whenever the subscription is not paying, whatever plan was bought. | | `status` | `string` | yes | Stripe's status for the subscription (`active`, `trialing`, `past_due`, `canceled`, `unpaid`, `incomplete`, and so on), or `none` when the account never started one. | | `currentPeriodEnd` | `integer \| null` | yes | When the period paid for ends, in milliseconds since the epoch, or null when there is no subscription. | | `cancelAtPeriodEnd` | `boolean` | yes | True while the subscription is scheduled to end at the close of the period already paid for. | | `hasCustomer` | `boolean` | yes | True once the account has billing details at Stripe, which is when the billing portal can be opened. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Cancel the plan at the end of the period > Page: https://docs.plotwork.dev/api/post-user-billing-cancel. Index of every page: https://docs.plotwork.dev/llms.txt Sets the subscription to end with the period already paid for. `POST /user/billing/cancel` ## TL;DR | Question | Answer | |---|---| | What does it do? | Cancel the plan at the end of the period. | | What does it take? | A JSON body of type [`CancelBody`](https://docs.plotwork.dev/api/schemas.md#cancelbody). | | What does it return? | Status 200 with a body of type [`BillingState`](https://docs.plotwork.dev/api/schemas.md#billingstate). | | What can go wrong? | `400` the body is not valid; `401` not signed in; `403` the request came from an origin that may not make changes; `409` there is no paid plan to cancel, or it is already set to end; `429` too many requests; `502` stripe could not complete the request; `503` billing is not set up on this server. Every failure uses the one error envelope. | ## What it does Sets the subscription to end with the period already paid for. Pro stays until then. The optional reason is sent to Stripe as the cancellation's comment and is not stored by Plotwork. 409 when there is no paid plan or it is already set to end. ## Request body The body has the type [`CancelBody`](https://docs.plotwork.dev/api/schemas.md#cancelbody). | Field | Type | Required | Description | |---|---|---|---| | `reason` | `string` | no | Why the person is cancelling, in their own words. Optional. Sent to Stripe as the cancellation comment; Plotwork does not store it. at most 500 characters. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The account's plan after the change. | [`BillingState`](https://docs.plotwork.dev/api/schemas.md#billingstate) | | 400 | The body is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | There is no paid plan to cancel, or it is already set to end. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 502 | Stripe could not complete the request. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Billing is not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`BillingState`](https://docs.plotwork.dev/api/schemas.md#billingstate). | Field | Type | Required | Description | |---|---|---|---| | `plan` | `"free" \| "pro"` | yes | The plan the account gets right now. It is the free plan whenever the subscription is not paying, whatever plan was bought. | | `status` | `string` | yes | Stripe's status for the subscription (`active`, `trialing`, `past_due`, `canceled`, `unpaid`, `incomplete`, and so on), or `none` when the account never started one. | | `currentPeriodEnd` | `integer \| null` | yes | When the period paid for ends, in milliseconds since the epoch, or null when there is no subscription. | | `cancelAtPeriodEnd` | `boolean` | yes | True while the subscription is scheduled to end at the close of the period already paid for. | | `hasCustomer` | `boolean` | yes | True once the account has billing details at Stripe, which is when the billing portal can be opened. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Take back a cancellation > Page: https://docs.plotwork.dev/api/post-user-billing-resume. Index of every page: https://docs.plotwork.dev/llms.txt Undoes a cancellation made earlier in the period, so the subscription renews. `POST /user/billing/resume` ## TL;DR | Question | Answer | |---|---| | What does it do? | Take back a cancellation. | | What does it take? | Nothing. | | What does it return? | Status 200 with a body of type [`BillingState`](https://docs.plotwork.dev/api/schemas.md#billingstate). | | What can go wrong? | `401` not signed in; `403` the request came from an origin that may not make changes; `409` there is no paid plan, or it is not set to end; `429` too many requests; `502` stripe could not complete the request; `503` billing is not set up on this server. Every failure uses the one error envelope. | ## What it does Undoes a cancellation made earlier in the period, so the subscription renews. 409 when there is no paid plan or it is not set to end. ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The account's plan after the change. | [`BillingState`](https://docs.plotwork.dev/api/schemas.md#billingstate) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | There is no paid plan, or it is not set to end. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 502 | Stripe could not complete the request. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Billing is not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`BillingState`](https://docs.plotwork.dev/api/schemas.md#billingstate). | Field | Type | Required | Description | |---|---|---|---| | `plan` | `"free" \| "pro"` | yes | The plan the account gets right now. It is the free plan whenever the subscription is not paying, whatever plan was bought. | | `status` | `string` | yes | Stripe's status for the subscription (`active`, `trialing`, `past_due`, `canceled`, `unpaid`, `incomplete`, and so on), or `none` when the account never started one. | | `currentPeriodEnd` | `integer \| null` | yes | When the period paid for ends, in milliseconds since the epoch, or null when there is no subscription. | | `cancelAtPeriodEnd` | `boolean` | yes | True while the subscription is scheduled to end at the close of the period already paid for. | | `hasCustomer` | `boolean` | yes | True once the account has billing details at Stripe, which is when the billing portal can be opened. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Open the Stripe billing portal > Page: https://docs.plotwork.dev/api/post-user-billing-portal. Index of every page: https://docs.plotwork.dev/llms.txt Makes a Stripe Billing Portal session for the account and returns its address. `POST /user/billing/portal` ## TL;DR | Question | Answer | |---|---| | What does it do? | Open the Stripe billing portal. | | What does it take? | Nothing. | | What does it return? | Status 200 with a body of type [`BillingUrl`](https://docs.plotwork.dev/api/schemas.md#billingurl). | | What can go wrong? | `401` not signed in; `403` the request came from an origin that may not make changes; `409` the account has no billing details at stripe yet; `429` too many requests; `502` stripe could not complete the request; `503` billing is not set up on this server. Every failure uses the one error envelope. | ## What it does Makes a Stripe Billing Portal session for the account and returns its address. An account that never started a checkout has no billing details yet and gets 409. ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The address of the Stripe page. | [`BillingUrl`](https://docs.plotwork.dev/api/schemas.md#billingurl) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 403 | The request came from an origin that may not make changes. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 409 | The account has no billing details at Stripe yet. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 502 | Stripe could not complete the request. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Billing is not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`BillingUrl`](https://docs.plotwork.dev/api/schemas.md#billingurl). | Field | Type | Required | Description | |---|---|---|---| | `url` | `string` | yes | An address on Stripe's own site. Send the browser there; it expires, so do not store it. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # List your invoices and receipts > Page: https://docs.plotwork.dev/api/get-user-billing-documents. Index of every page: https://docs.plotwork.dev/llms.txt The invoices Stripe issued for this account's Plotwork subscription, newest first. `GET /user/billing/documents` ## TL;DR | Question | Answer | |---|---| | What does it do? | List your invoices and receipts. | | What does it take? | Nothing. | | What does it return? | Status 200 with a body of type [`BillingDocumentList`](https://docs.plotwork.dev/api/schemas.md#billingdocumentlist). | | What can go wrong? | `401` not signed in; `429` too many requests; `502` stripe could not complete the request; `503` billing is not set up on this server. Every failure uses the one error envelope. | ## What it does The invoices Stripe issued for this account's Plotwork subscription, newest first. Amounts are whole cents with a currency. A paid invoice is a receipt. Nothing of another product on the shared Stripe account is ever listed. ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The account's documents. | [`BillingDocumentList`](https://docs.plotwork.dev/api/schemas.md#billingdocumentlist) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 502 | Stripe could not complete the request. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Billing is not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`BillingDocumentList`](https://docs.plotwork.dev/api/schemas.md#billingdocumentlist). | Field | Type | Required | Description | |---|---|---|---| | `items` | `BillingDocument[]` | yes | | | `items[].id` | `string` | yes | The id to ask for the document and its PDF with. | | `items[].kind` | `"invoice" \| "receipt"` | yes | A `receipt` is an invoice that has been paid; anything else is an `invoice`. | | `items[].number` | `string` | yes | The number printed on the document, which an accounts department quotes back. | | `items[].description` | `string` | yes | What it was for, such as the plan. Empty when Stripe names nothing. | | `items[].currency` | `string` | yes | The three-letter currency, in capitals. at least 3 characters; at most 3 characters. | | `items[].totalCents` | `integer` | yes | The total in whole cents (the currency's minor unit). | | `items[].status` | `string` | yes | The invoice's own word: `paid`, `open`, `void`, or `uncollectible`. A draft is never listed. | | `items[].issuedAt` | `integer` | yes | When it was issued, in milliseconds since the epoch. | | `items[].paidAt` | `integer \| null` | yes | When it was paid, in milliseconds since the epoch, or null while it is not. | | `items[].downloadable` | `boolean` | yes | False when there is no PDF to fetch, so the PDF route would answer 404. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Read one invoice or receipt > Page: https://docs.plotwork.dev/api/get-user-billing-documents-id. Index of every page: https://docs.plotwork.dev/llms.txt One document with its lines, totals, who it is billed to, the issuer, and the card as a brand and last four digits. `GET /user/billing/documents/{id}` ## TL;DR | Question | Answer | |---|---| | What does it do? | Read one invoice or receipt. | | What does it take? | `id` in the path. | | What does it return? | Status 200 with a body of type [`BillingDocumentDetail`](https://docs.plotwork.dev/api/schemas.md#billingdocumentdetail). | | What can go wrong? | `400` the id is not shaped like an invoice id; `401` not signed in; `404` no such document for this account; `429` too many requests; `502` stripe could not complete the request; `503` billing is not set up on this server. Every failure uses the one error envelope. | ## What it does One document with its lines, totals, who it is billed to, the issuer, and the card as a brand and last four digits. A document that does not exist, is another account's, or is not Plotwork's is the same 404. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The document id, as the list gave it. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The document. | [`BillingDocumentDetail`](https://docs.plotwork.dev/api/schemas.md#billingdocumentdetail) | | 400 | The id is not shaped like an invoice id. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such document for this account. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 502 | Stripe could not complete the request. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Billing is not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`BillingDocumentDetail`](https://docs.plotwork.dev/api/schemas.md#billingdocumentdetail). | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The id to ask for the document and its PDF with. | | `kind` | `"invoice" \| "receipt"` | yes | A `receipt` is an invoice that has been paid; anything else is an `invoice`. | | `number` | `string` | yes | The number printed on the document, which an accounts department quotes back. | | `description` | `string` | yes | What it was for, such as the plan. Empty when Stripe names nothing. | | `currency` | `string` | yes | The three-letter currency, in capitals. at least 3 characters; at most 3 characters. | | `totalCents` | `integer` | yes | The total in whole cents (the currency's minor unit). | | `status` | `string` | yes | The invoice's own word: `paid`, `open`, `void`, or `uncollectible`. A draft is never listed. | | `issuedAt` | `integer` | yes | When it was issued, in milliseconds since the epoch. | | `paidAt` | `integer \| null` | yes | When it was paid, in milliseconds since the epoch, or null while it is not. | | `downloadable` | `boolean` | yes | False when there is no PDF to fetch, so the PDF route would answer 404. | | `lines` | `DocumentLine[]` | yes | | | `lines[].description` | `string` | yes | | | `lines[].quantity` | `integer` | yes | minimum 1. | | `lines[].amountCents` | `integer` | yes | | | `lines[].periodStart` | `integer \| null` | yes | The start of the service period, in milliseconds since the epoch, or null. | | `lines[].periodEnd` | `integer \| null` | yes | The end of the service period, in milliseconds since the epoch, or null. | | `subtotalCents` | `integer \| null` | yes | | | `taxCents` | `integer \| null` | yes | The tax, or null when Stripe names none. | | `amountPaidCents` | `integer \| null` | yes | | | `billedTo` | `object` | yes | | | `billedTo.name` | `string \| null` | yes | | | `billedTo.email` | `string \| null` | yes | | | `issuer` | `object \| null` | yes | The Stripe account's business name and its tax numbers, from the invoice. There is no address: Stripe does not put one on an invoice. | | `issuer.name` | `string` | yes | | | `issuer.lines` | `string[]` | yes | | | `paidWith` | `object \| null` | yes | The card the payment was taken from: its brand and last four digits, and nothing more of it. Null when it was not paid by card. | | `paidWith.brand` | `string` | yes | | | `paidWith.last4` | `string` | yes | pattern `^\d{4}$`. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Download an invoice or receipt as a PDF > Page: https://docs.plotwork.dev/api/get-user-billing-documents-id-pdf. Index of every page: https://docs.plotwork.dev/llms.txt The PDF Stripe made for the invoice, as a file to save. `GET /user/billing/documents/{id}/pdf` ## TL;DR | Question | Answer | |---|---| | What does it do? | Download an invoice or receipt as a PDF. | | What does it take? | `id` in the path. | | What does it return? | Status 200 with no body. | | What can go wrong? | `400` the id is not shaped like an invoice id; `401` not signed in; `404` no such document for this account, or it has no pdf; `429` too many requests; `502` stripe could not supply the pdf; `503` billing is not set up on this server. Every failure uses the one error envelope. | ## What it does The PDF Stripe made for the invoice, as a file to save. Only the address Stripe returned on this account's own invoice is fetched, and only if it is https on a Stripe host. Never cached. 404 for a document with no PDF. ## Path parameters | Field | Type | Required | Description | |---|---|---|---| | `id` | `string` | yes | The document id, as the list gave it. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The PDF. | none | | 400 | The id is not shaped like an invoice id. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | Not signed in. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 404 | No such document for this account, or it has no PDF. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 502 | Stripe could not supply the PDF. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Billing is not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Receive a Stripe event. Called by Stripe, not by clients > Page: https://docs.plotwork.dev/api/post-billing-webhook. Index of every page: https://docs.plotwork.dev/llms.txt Called by Stripe, not by clients. `POST /billing/webhook` ## TL;DR | Question | Answer | |---|---| | What does it do? | Receive a Stripe event. Called by Stripe, not by clients. | | What does it take? | A JSON body of type `object`. | | What does it return? | Status 200 with a body of type [`WebhookAck`](https://docs.plotwork.dev/api/schemas.md#webhookack). | | What can go wrong? | `400` the stripe-signature header is missing; `401` the signature is not valid; `413` the body is larger than the cap; `429` too many requests; `500` the event could not be handled; stripe will send it again; `503` billing is not set up on this server. Every failure uses the one error envelope. | ## What it does Called by Stripe, not by clients. The body is a Stripe event, authenticated only by the signature in the `Stripe-Signature` header, checked against the exact bytes received. An event that is not about a Plotwork subscription is acknowledged and ignored; an event handled before is acknowledged as a duplicate; a handler that fails answers 500 so that Stripe sends the event again. ## Header parameters | Field | Type | Required | Description | |---|---|---|---| | `stripe-signature` | `string` | yes | The signature Stripe computes over the timestamp and the exact request body with this endpoint's signing secret. | ## Request body The body has the type `object`. | Field | Type | Required | Description | |---|---|---|---| ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The event was received. | [`WebhookAck`](https://docs.plotwork.dev/api/schemas.md#webhookack) | | 400 | The Stripe-Signature header is missing. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 401 | The signature is not valid. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than the cap. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 500 | The event could not be handled; Stripe will send it again. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | Billing is not set up on this server. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`WebhookAck`](https://docs.plotwork.dev/api/schemas.md#webhookack). | Field | Type | Required | Description | |---|---|---|---| | `received` | `true` | yes | | | `outcome` | `"handled" \| "duplicate" \| "ignored"` | yes | `handled` when the event changed or confirmed an account, `duplicate` when it had been handled before, `ignored` when it is not about a Plotwork subscription. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Send an Enterprise enquiry > Page: https://docs.plotwork.dev/api/post-enterprise-enquiries. Index of every page: https://docs.plotwork.dev/llms.txt For the Enterprise page of the Plotwork site and the Talk to sales form of the portal. `POST /enterprise/enquiries` ## TL;DR | Question | Answer | |---|---| | What does it do? | Send an Enterprise enquiry. | | What does it take? | A JSON body of type [`EnquiryBody`](https://docs.plotwork.dev/api/schemas.md#enquirybody). | | What does it return? | Status 200 with a body of type [`EnquiryAck`](https://docs.plotwork.dev/api/schemas.md#enquiryack). | | What can go wrong? | `400` a field is missing, too long, or not one of the choices. the answer names each field in `details`; `413` the body is larger than 16 kib; `429` too many requests from this address; `503` this server has no database. Every failure uses the one error envelope. | ## What it does For the Enterprise page of the Plotwork site and the Talk to sales form of the portal. Anyone may send an enquiry, signed in or not. The body is the name, work email, company, company size, number of repositories, what the company needs and use case the person typed, and from the portal where the form was opened (`context`). When the request carries a session cookie from an origin on the allowlist, the enquiry is linked to that account, its address replaces the one in the body, and the organisation `context.org` names is kept only when the account is a member; the account is never read from the body. Without a session the enquiry is linked to nothing. Plotwork stores those fields and the time, and emails them to its own inbox with the person as the reply-to; it stores no address, header or cookie with them, and deletes an enquiry 24 months after it was sent. The answer is `{ "received": true }` whatever happens after the checks, and never names where the email went. An enquiry whose `website` field is filled, or that was sent faster than a person fills the form (`elapsedMs`), is dropped with the same answer. The route is rate limited by the caller's address at the `tight` tier, which Cloudflare's rate limiter holds as a counter key for 60 seconds; Plotwork's own code does not store it. The body is capped at 16 KiB. ## Request body The body has the type [`EnquiryBody`](https://docs.plotwork.dev/api/schemas.md#enquirybody). | Field | Type | Required | Description | |---|---|---|---| | `name` | `string` | yes | The name of the person asking. at least 1 character; at most 120 characters. | | `email` | `string` | yes | Their work email address. The reply goes here. | | `company` | `string` | yes | The company. at least 1 character; at most 120 characters. | | `companySize` | `"1-49" \| "50-249" \| "250-999" \| "1000-4999" \| "5000+"` | yes | How many people the company has. | | `repos` | `"1-9" \| "10-49" \| "50-199" \| "200-999" \| "1000+"` | yes | How many repositories the team would run diagrams in. | | `useCase` | `string` | yes | What the team wants to do, in their words. at least 10 characters; at most 2000 characters. | | `interests` | `("sso-scim" \| "audit-siem" \| "support-sla" \| "limits" \| "self-hosting" \| "invoicing")[]` | no | What the company needs, as the boxes ticked on the form. Left out, nothing was ticked; a box named twice counts once. | | `context` | `EnquiryContext` | no | Sent by the portal: where the form was opened. The site sends none. | | `context.page` | `"contact" \| "plan" \| "sso" \| "integrations" \| "support" \| "audit"` | yes | The place in the portal the form was opened from, such as `sso` for the Single sign-on panel. | | `context.org` | `string` | no | The slug of the organisation the form was opened from. Kept only when the signed-in account is a member. pattern `^[a-z0-9](?:[a-z0-9-]{1,38}[a-z0-9])$`. | | `website` | `string` | no | Leave empty. A field people do not see: an enquiry that fills it is taken for a script and dropped, with the same answer. at most 200 characters. | | `elapsedMs` | `integer` | yes | Milliseconds between the form being shown and being sent. An enquiry sent faster than a person can type is dropped, with the same answer. minimum 0. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | Received. The same answer whether the enquiry was kept or dropped as a script. | [`EnquiryAck`](https://docs.plotwork.dev/api/schemas.md#enquiryack) | | 400 | A field is missing, too long, or not one of the choices. The answer names each field in `details`. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than 16 KiB. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests from this address. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | This server has no database. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`EnquiryAck`](https://docs.plotwork.dev/api/schemas.md#enquiryack). | Field | Type | Required | Description | |---|---|---|---| | `received` | `true` | yes | Always true. The enquiry was received. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Receive the opt-in daily totals of the local npm package > Page: https://docs.plotwork.dev/api/post-telemetry-usage. Index of every page: https://docs.plotwork.dev/llms.txt For the local npm package (`@buun_group/plotwork-mcp`) only, and only when the person who runs it has set `PLOTWORK_USAGE=share`; it is off by default, and setting `DO_NOT_TRACK=1` stops the package from ever sending. `POST /telemetry/usage` ## TL;DR | Question | Answer | |---|---| | What does it do? | Receive the opt-in daily totals of the local npm package. | | What does it take? | A JSON body of type [`LocalUsageBody`](https://docs.plotwork.dev/api/schemas.md#localusagebody). | | What does it return? | Status 200 with a body of type [`LocalUsageAck`](https://docs.plotwork.dev/api/schemas.md#localusageack). | | What can go wrong? | `400` the body is not valid json, has a field the schema does not name, or breaks a bound. the answer lists the first problems; `413` the body is larger than 64 kib; `429` too many requests; `503` this server has no database. Every failure uses the one error envelope. | ## What it does For the local npm package (`@buun_group/plotwork-mcp`) only, and only when the person who runs it has set `PLOTWORK_USAGE=share`; it is off by default, and setting `DO_NOT_TRACK=1` stops the package from ever sending. The body is pseudonymous daily totals for finished days: a random install id that changes every month, counts of how often each local tool ended in each way for each kind of diagram, the package version, the operating system family, and the Node major version. It never carries diagram source, tool arguments or results, names, folders, paths, error text, host names, user names, environment variables, or the time of day. Plotwork stores no address, header, account, or time beyond the day with it. The route is rate limited by the caller's address, which Cloudflare's rate limiter holds as a counter key for 60 seconds; Plotwork's own code does not store it. There is no session, key, or cookie. A count whose tool or kind the server does not know is dropped and counted as ignored. A count sent again replaces the one stored. The totals are indicative: anyone can post to a public route, so they are never used for limits, billing, or anything about an account. Rows are kept for 400 days. ## Request body The body has the type [`LocalUsageBody`](https://docs.plotwork.dev/api/schemas.md#localusagebody). | Field | Type | Required | Description | |---|---|---|---| | `schema` | `1` | yes | The version of this request shape. Always 1. | | `client` | `LocalUsageClient` | yes | | | `client.name` | `"plotwork-mcp"` | yes | Always `plotwork-mcp`. | | `client.version` | `string` | yes | The package version, in semver form. at most 40 characters; pattern `^\d{1,6}\.\d{1,6}\.\d{1,6}(?:-[0-9A-Za-z.-]{1,32})?(?:\+[0-9A-Za-z.-]{1,32})?$`. | | `client.os` | `"linux" \| "darwin" \| "win32" \| "other"` | yes | The operating system family. Anything else is `other`. | | `client.node` | `integer` | yes | The Node major version, a whole number. minimum 18; maximum 99. | | `days` | `LocalUsageDay[]` | yes | At most 31 finished days. | | `days[].day` | `string` | yes | A UTC date from 40 days ago to yesterday. Nothing is accepted for today. pattern `^\d{4}-\d{2}-\d{2}$`. | | `days[].id` | `string` | yes | A pseudonym for one install for one month: the first 128 bits of an HMAC-SHA-256 of `plotwork-usage:` and the month, keyed with a secret that never leaves the device. It changes every month, so installs can be counted within a month and cannot be followed from one month to the next. pattern `^[0-9a-f]{32}$`. | | `days[].counts` | `LocalUsageCount[]` | yes | At most 400 counts. A tool, kind, and outcome may appear once a day. | | `days[].counts[].tool` | `string` | yes | The name of the local MCP tool, such as `draw_diagram`. A name this server does not know is ignored, not refused, so an older server accepts a newer package. pattern `^[a-z][a-z0-9_]{0,63}$`. | | `days[].counts[].kind` | `string` | yes | The diagram kind the source declared, such as `flow`, or `none` for a call with no source or one that names no kind. A kind this server does not know is ignored, not refused, so an older server accepts a newer package. pattern `^[a-z][a-z0-9]{0,31}$`. | | `days[].counts[].outcome` | `"ok" \| "rejected" \| "error"` | yes | `ok` produced its answer, `rejected` was refused because of its input, `error` failed inside the tool. | | `days[].counts[].count` | `integer` | yes | A whole number from 1 to 1,000,000. minimum 1; maximum 1000000. | ## Responses | Status | Meaning | Body | |---|---|---| | 200 | The request was understood: how many counts were stored, and how many were ignored because their tool is not known. | [`LocalUsageAck`](https://docs.plotwork.dev/api/schemas.md#localusageack) | | 400 | The body is not valid JSON, has a field the schema does not name, or breaks a bound. The answer lists the first problems. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 413 | The body is larger than 64 KiB. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 429 | Too many requests. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | | 503 | This server has no database. | [`ErrorResponse`](https://docs.plotwork.dev/api/schemas.md#errorresponse) | ## Response body A 200 answer has the type [`LocalUsageAck`](https://docs.plotwork.dev/api/schemas.md#localusageack). | Field | Type | Required | Description | |---|---|---|---| | `accepted` | `integer` | yes | How many counts were stored. A count sent again replaces the one stored, so a retry never adds twice. minimum 0. | | `ignored` | `integer` | yes | How many counts were dropped because their tool or kind is not one this server knows. A 200 means the client may treat the days as sent. minimum 0. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Check the service is up > Page: https://docs.plotwork.dev/api/get-health. Index of every page: https://docs.plotwork.dev/llms.txt Always answers 200 with the package version and the diagram kinds this build renders. `GET /health` ## TL;DR | Question | Answer | |---|---| | What does it do? | Check the service is up. | | What does it take? | Nothing. | | What does it return? | Status 200 with a body of type [`HealthResponse`](https://docs.plotwork.dev/api/schemas.md#healthresponse). | | What can go wrong? | Nothing beyond a server error. | ## What it does Always answers 200 with the package version and the diagram kinds this build renders. ## Responses | Status | Meaning | Body | |---|---|---| | 200 | Service is up. | [`HealthResponse`](https://docs.plotwork.dev/api/schemas.md#healthresponse) | ## Response body A 200 answer has the type [`HealthResponse`](https://docs.plotwork.dev/api/schemas.md#healthresponse). | Field | Type | Required | Description | |---|---|---|---| | `status` | `"ok"` | yes | | | `version` | `string` | yes | Package version of the running service. | | `kinds` | `string[]` | yes | Diagram kinds this build can render. | ## See also | Doc | Why | |---|---| | [API reference](https://docs.plotwork.dev/api/reference.md) | Every endpoint, and the error envelope | | [Schemas](https://docs.plotwork.dev/api/schemas.md) | Every request and response type | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The operations as callable tools | --- # Language reference > Page: https://docs.plotwork.dev/reference/language. Index of every page: https://docs.plotwork.dev/llms.txt Every word the diagram language accepts: kinds, node types, groups, arrows, tones, icons, limits, and diagnostic codes. Each topic has its own page, so a reader or an agent loads only what the task needs. ## TL;DR | Question | Answer | |---|---| | What starts a diagram? | A header line naming the kind and a quoted title: `flow "Title"`, `sequence "Title"`, `schema "Title"`, `git "Title"`, `gantt "Title"`, `class "Title"`, `state "Title"`, `timeline "Title"`, `journey "Title"`, `mindmap "Title"`, `treemap "Title"`, `pie "Title"`, `quadrant "Title"`, `layers "Title"`, `c4 "Title"`, `matrix "Title"`, `sankey "Title"`, `xychart "Title"`, `tree "Title"`, `swimlane "Title"`. | | Who picks colors? | The theme. Authors choose a type or a tone (neutral, blue, violet, aqua, orange, green, amber, red), never a color value. | | How many icons are bundled? | 2283, in 12 sets. Use `search_icons` or `map_icons` to find names. | | How big can a diagram be? | 50000 characters of source. Quality warnings start at 15 flow nodes (one more for each group, up to 25), 6 tables, 6 actors, 40 git graph commits, 7 git graph branches, 40 Gantt chart tasks and milestones, 12 class diagram classes, 12 class diagram members in one class, 15 state states, 3 state composite nesting, 12 timeline periods on a horizontal rail, 24 timeline periods on a vertical rail, 5 timeline events in one period, 48 timeline events, 16 user journey tasks, 8 user journey actors, 40 mind map topics, 8 mind map branches, 4 mind map levels, 9 mind map topics under one topic, 80 treemap treemap items, 6 share chart slices, 3 share chart points between slices, 40 quadrant chart points, 8 layered architecture blocks in a layer, 12 C4 diagram elements, 20 matrix rows, 12 matrix columns, 30 matrix items, 8 Sankey diagram sankey columns, 6 XY chart series, 60 XY chart places, 60 tree tree rows, 8 swimlane lanes, or 40 swimlane steps. | | Where do I start for a common architecture? | From a pattern: `find_pattern` ranks ready-made, checked diagrams for a request, and `get_pattern` or `draw_from_pattern` fills one. See the [architecture patterns guide](https://docs.plotwork.dev/architecture-patterns.md). | | How do I draw a cloud architecture? | Give every group a `type=`, set `provider=` on the header, and give every service a provider icon found with `search_icons`. See the [cloud architecture guide](https://docs.plotwork.dev/cloud-architecture.md). | | What happens on a mistake? | Every error carries a line, a column, and a hint, and all of them are reported in one pass. | ## Pages | Page | What it covers | |---|---| | [Diagram kinds and settings](https://docs.plotwork.dev/reference/language/kinds.md) | The twenty kinds of diagram with an example of each, and the settings a diagram accepts: tones, directions, themes, sequence frames, and schema column flags. | | [Flow node types](https://docs.plotwork.dev/reference/language/flow-nodes.md) | Every type a flow node can take, with its shape, default tone and icon, the technology marks a node can carry, and the mark of a planned change. | | [Groups and overlays](https://docs.plotwork.dev/reference/language/groups.md) | How a group frames nodes and nested groups, the options it takes, and how an overlay draws a frame around members that layout placed apart. | | [Group types](https://docs.plotwork.dev/reference/language/group-types.md) | Every group type with its frame style and tone, the icon each draws for each provider, and a complete cloud architecture diagram. | | [Arrows and edge labels](https://docs.plotwork.dev/reference/language/edges.md) | The arrow tokens, the labels and icons an edge can carry, the level edge that keeps mirrored columns side by side, and the options that animate a line, number the steps, or color a line by what it carries. | | [Icon sets and status icons](https://docs.plotwork.dev/reference/language/icon-sets.md) | The icon sets a diagram can draw from, how a provider product is written, and the status icons that keep their own color. | | [Limits and quality checks](https://docs.plotwork.dev/reference/language/limits.md) | How large a diagram may be, the quality warnings that fire past the density limits, and the two suggestions that name an icon or a group type to add. | | [Diagnostic codes](https://docs.plotwork.dev/reference/language/diagnostics.md) | Every error and warning code with its severity and name, and how to read one. | ## See also | Doc | Why | |---|---| | [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md) | The language by example, kind by kind | | [Cloud architecture](https://docs.plotwork.dev/cloud-architecture.md) | Typed groups, provider icons, and how to map a product name to an icon | | [Architecture patterns](https://docs.plotwork.dev/architecture-patterns.md) | Ready-made, checked diagrams to start from, each with slots to rename | | [Diagrams in Markdown and on GitHub](https://docs.plotwork.dev/markdown-and-github.md) | How a fence becomes a picture | | [Icon reference](https://docs.plotwork.dev/reference/icons.md) | Every bundled icon and how to find one | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The same checks as callable tools | | [API reference](https://docs.plotwork.dev/api/reference.md) | The same checks over HTTP | --- # Diagram kinds and settings > Page: https://docs.plotwork.dev/reference/language/kinds. Index of every page: https://docs.plotwork.dev/llms.txt The twenty kinds of diagram with an example of each, and the settings a diagram accepts: tones, directions, themes, sequence frames, and schema column flags. ## TL;DR | Question | Answer | |---|---| | What starts a diagram? | A header line naming the kind and a quoted title: `flow "Title"`, `sequence "Title"`, `schema "Title"`, `git "Title"`, `gantt "Title"`, `class "Title"`, `state "Title"`, `timeline "Title"`, `journey "Title"`, `mindmap "Title"`, `treemap "Title"`, `pie "Title"`, `quadrant "Title"`, `layers "Title"`, `c4 "Title"`, `matrix "Title"`, `sankey "Title"`, `xychart "Title"`, `tree "Title"`, `swimlane "Title"`. | | Who picks colors? | The theme. Authors choose a type or a tone (neutral, blue, violet, aqua, orange, green, amber, red), never a color value. | | Which directions can a flow take? | `auto`, `right`, `down`, `left`, `up`, written `direction=<value>` on the header. | | Where is each kind explained in full? | Flow: [Flow node types](https://docs.plotwork.dev/reference/language/flow-nodes.md), [Groups and overlays](https://docs.plotwork.dev/reference/language/groups.md), and [Arrows and edge labels](https://docs.plotwork.dev/reference/language/edges.md). Sequence and schema: [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md). | ## Diagram kinds | Kind | Header line | |---|---| | `flow` | `flow "Title"` | | `sequence` | `sequence "Title"` | | `schema` | `schema "Title"` | | `git` | `git "Title"` | | `gantt` | `gantt "Title"` | | `class` | `class "Title"` | | `state` | `state "Title"` | | `timeline` | `timeline "Title"` | | `journey` | `journey "Title"` | | `mindmap` | `mindmap "Title"` | | `treemap` | `treemap "Title"` | | `pie` | `pie "Title"` | | `quadrant` | `quadrant "Title"` | | `layers` | `layers "Title"` | | `c4` | `c4 "Title"` | | `matrix` | `matrix "Title"` | | `sankey` | `sankey "Title"` | | `xychart` | `xychart "Title"` | | `tree` | `tree "Title"` | | `swimlane` | `swimlane "Title"` | ## Examples ### C4: Internet banking system **Diagram source** ```diagram c4 "Internet banking system" view=container desc="The containers of the internet banking system and the people and systems around it." person customer "Personal banking customer" desc="A customer of the bank." system mainframe "Mainframe banking system" external=on desc="Stores all the core banking data." system email "Email system" external=on desc="The bank's email system." boundary bank "Internet banking system" kind=system { container web "Web application" tech="Spring MVC" desc="Delivers the static content and the single-page app." container spa "Single-page app" tech="Angular" desc="Provides the banking features in the browser." container mobile "Mobile app" tech="Xamarin" desc="Provides a limited set of banking features." container api "API application" tech="Spring Boot" desc="Provides the banking features as a JSON API." db store "Database" tech="Oracle" desc="Stores accounts, credentials, and audit logs." } customer -> web : "Visits bigbank.com" tech="HTTPS" customer -> spa : "Views balances and pays" customer -> mobile : "Views balances and pays" web -> spa : "Delivers" spa -> api : "Makes API calls to" tech="JSON/HTTPS" mobile -> api : "Makes API calls to" tech="JSON/HTTPS" api -> store : "Reads and writes" tech="JDBC" api -> mainframe : "Makes API calls to" tech="XML/HTTPS" api --> email : "Sends email using" tech="SMTP" email --> customer : "Sends emails to" ``` *Picture: Internet banking system: container view, 8 elements, 1 boundary, 10 relationships. The containers of the internet banking system and the people and systems around it.* **Text description** **Elements:** - Personal banking customer, person (A customer of the bank.) - Mainframe banking system, external system (Stores all the core banking data.) - Email system, external system (The bank's email system.) - Boundary: Internet banking system, software system - Web application, container, Spring MVC (Delivers the static content and the single-page app.) - Single-page app, container, Angular (Provides the banking features in the browser.) - Mobile app, container, Xamarin (Provides a limited set of banking features.) - API application, container, Spring Boot (Provides the banking features as a JSON API.) - Database, database, Oracle (Stores accounts, credentials, and audit logs.) **Relationships:** - Personal banking customer to Web application: Visits bigbank.com, HTTPS - Personal banking customer to Single-page app: Views balances and pays - Personal banking customer to Mobile app: Views balances and pays - Web application to Single-page app: Delivers - Single-page app to API application: Makes API calls to, JSON/HTTPS - Mobile app to API application: Makes API calls to, JSON/HTTPS - API application to Database: Reads and writes, JDBC - API application to Mainframe banking system: Makes API calls to, XML/HTTPS - API application to Email system (dashed): Sends email using, SMTP - Email system to Personal banking customer (dashed): Sends emails to ### Class: Notification service **Diagram source** ```diagram class "Notification service" desc="A notifier queues messages and delivers each one through a channel. Channels are interchangeable, so a new one needs no change to the notifier." class Notifier stereotype="service" { "-queue: Queue<Message>" "+send(m: Message): void" "+retry(): int" } class Message { "+id: uuid" "+body: String" "+priority: Priority" } class Priority type=enum { "LOW" "NORMAL" "URGENT" } class Template "Template<T>" type=abstract { "+render(data: T): String" abstract "+named(n: String): Template" static } package channels "Channels" { class Channel type=interface { "+deliver(m: Message): Ack" } class Email "EmailChannel" { "-host: String" "+deliver(m: Message): Ack" } class Sms "SmsChannel" { "-gateway: String" "+deliver(m: Message): Ack" } } Notifier "1" owns "*" Message : "queues" Notifier "1" has "1..*" Channel : "delivers through" Notifier --> Template : "renders with" Message --> Priority Email implements Channel Sms implements Channel note on Message : "Kept until every channel has confirmed delivery." ``` *Picture: Notification service: 7 classes, 1 package, 6 relations. A notifier queues messages and delivers each one through a channel. Channels are interchangeable, so a new one needs no change to the notifier.* **Text description** **Classes:** - Notifier: class, service - private queue: Queue<Message> - public send(m: Message): void - public retry(): int - Message: class - public id: uuid - public body: String - public priority: Priority - Priority: enum - LOW - NORMAL - URGENT - Template<T>: abstract class - public render(data: T): String (abstract) - public named(n: String): Template (static) - Package Channels: - Channel: interface - public deliver(m: Message): Ack - EmailChannel: class - private host: String - public deliver(m: Message): Ack - SmsChannel: class - private gateway: String - public deliver(m: Message): Ack **Relations:** - Notifier (1) is composed of Message (*): queues - Notifier (1) aggregates Channel (1..*): delivers through - Notifier depends on Template<T>: renders with - Message depends on Priority - EmailChannel implements Channel - SmsChannel implements Channel **Notes:** - Note on Message: Kept until every channel has confirmed delivery. ### Flow: Login request **Diagram source** ```diagram flow "Login request" desc="A user signs in through an API that checks credentials in a database and keeps the session in a cache." node user person "User" node api service "Auth API" sub="Validates credentials" group data "Data" { node db database "PostgreSQL" sub="Password hashes" node cache cache "Redis" sub="Sessions" } user -> api : "signs in" role=identity api -> db : "looks up user" role=data db --> api : "row" api -> cache : "stores session" role=data ``` *Picture: Login request: 4 nodes, 4 connections. A user signs in through an API that checks credentials in a database and keeps the session in a cache.* **Text description** **Nodes:** - User, person - Auth API (Validates credentials), service - Data tier: Data - PostgreSQL (Password hashes), database - Redis (Sessions), cache **Connections:** - User to Auth API (identity line): signs in - Auth API to PostgreSQL (stored data line): looks up user - PostgreSQL to Auth API (dashed): row - Auth API to Redis (stored data line): stores session ### Gantt: Product launch plan **Diagram source** ```diagram gantt "Product launch plan" start=2026-01-05 unit=week today=2026-02-11 weekends=skip holidays=2026-04-03 desc="A fourteen week plan from research to launch, with design and build running side by side." section "Discover" { task research "User research" start=2026-01-05 duration=2w status=done task brief "Product brief" after=research duration=1w status=done } section "Design" tone=violet { task ux "UX flows" after=brief duration=2w status=done task ui "Visual design" after=ux duration=3w status=active task ds "Design system" after=brief duration=4w status=active } section "Build" tone=aqua { task api "API and data model" after=brief duration=5w status=active task web "Web app" after=ui,api duration=4w status=critical task mobile "Mobile app" after=ui,api duration=4w milestone beta "Beta ready" after=web } section "Launch" tone=orange { task docs "Help center" after=ui duration=3w task qa "Testing and fixes" after=web,mobile duration=2w status=critical milestone ga "Public launch" after=qa status=critical } ``` *Picture: Product launch plan: 10 tasks and 2 milestones in 4 sections, from 2026-01-05 to 2026-04-14. A fourteen week plan from research to launch, with design and build running side by side.* **Text description** **Section Discover (2026-01-05 to 2026-01-23):** - User research: 2026-01-05 to 2026-01-16, 12 days, done - Product brief: 2026-01-19 to 2026-01-23, 5 days, done, after User research **Section Design (2026-01-26 to 2026-02-27):** - UX flows: 2026-01-26 to 2026-02-06, 12 days, done, after Product brief - Visual design: 2026-02-09 to 2026-02-27, 19 days, active, after UX flows - Design system: 2026-01-26 to 2026-02-20, 26 days, active, after Product brief **Section Build (2026-01-26 to 2026-03-30):** - API and data model: 2026-01-26 to 2026-02-27, 33 days, active, after Product brief - Web app: 2026-03-02 to 2026-03-27, 26 days, critical, after Visual design and API and data model - Mobile app: 2026-03-02 to 2026-03-27, 26 days, after Visual design and API and data model - Beta ready: milestone on 2026-03-30, after Web app **Section Launch (2026-03-02 to 2026-04-14):** - Help center: 2026-03-02 to 2026-03-20, 19 days, after Visual design - Testing and fixes: 2026-03-30 to 2026-04-13, 15 days, critical, after Web app and Mobile app - Public launch: milestone on 2026-04-14, critical, after Testing and fixes **Today is 2026-02-11.** **Weekends are shaded and not counted in durations.** **Days off: 2026-04-03.** ### Git: Release and hotfix **Diagram source** ```diagram git "Release and hotfix" desc="A feature is merged into develop, develop is released as v1.0, and a hotfix is tagged and picked back." commit "Initial commit" branch develop order=2 commit "Set up CI" branch "feature/search" from=develop commit "Add search box" commit "Index the docs" checkout develop merge "feature/search" "Add search" checkout main merge develop "Release 1.0" tag="v1.0" type=highlight branch hotfix order=1 commit "Fix null check" id=fix checkout main merge hotfix tag="v1.0.1" checkout develop cherry-pick fix commit "Revert flaky test" type=reverse ``` *Picture: Release and hotfix: 10 commits on 4 branches, 3 merges, 1 cherry-pick, 2 tags. A feature is merged into develop, develop is released as v1.0, and a hotfix is tagged and picked back.* **Text description** **Branches, in lane order:** - main is the default branch - hotfix was made from main at "Release 1.0" - develop was made from main at "Initial commit" - feature/search was made from develop at "Set up CI" **History, oldest first:** **1. On main: commit "Initial commit"** - develop was made from main at "Initial commit" **2. On develop: commit "Set up CI"** - feature/search was made from develop at "Set up CI" **3. On feature/search: commit "Add search box"** **4. On feature/search: commit "Index the docs"** **5. On develop: merge of feature/search into develop ("Add search")** **6. On main: merge of develop into main ("Release 1.0"), highlighted, tagged v1.0** - hotfix was made from main at "Release 1.0" **7. On hotfix: commit "Fix null check", id fix** **8. On main: merge of hotfix into main, tagged v1.0.1** **9. On develop: cherry-pick of fix ("Fix null check")** **10. On develop: commit "Revert flaky test", a revert** ### Journey: Checkout experience **Diagram source** ```diagram journey "Checkout experience" desc="How a shopper feels from finding a product to holding the parcel, and who is involved at each step." actor "Customer" tone=blue actor "Support agent" tone=violet actor "Bank" actor "Courier" section "Browse" { task "Find a product" score=5 actors="Customer" task "Compare prices" score=3 actors="Customer, Support agent" sub="Asks about delivery" } section "Pay" { task "Enter card details" score=2 actors="Customer" task "Confirm with the bank" score=1 actors="Customer, Bank" task "Get a receipt" score=4 actors="Customer" } section "Deliver" { task "Track the parcel" score=4 actors="Customer, Courier" task "Receive it" score=5 actors="Customer, Courier" } ``` *Picture: Checkout experience: 7 tasks in 3 stages, average satisfaction 3.4 of 5. How a shopper feels from finding a product to holding the parcel, and who is involved at each step.* **Text description** **Satisfaction runs from 1 (frustrated) to 5 (delighted).** - Browse: 2 tasks, average 4.0 1. Find a product: 5 of 5 (delighted), with Customer 2. Compare prices: 3 of 5 (neutral), with Customer and Support agent. Asks about delivery - Pay: 3 tasks, average 2.3 3. Enter card details: 2 of 5 (unhappy), with Customer 4. Confirm with the bank: 1 of 5 (frustrated), with Customer and Bank 5. Get a receipt: 4 of 5 (happy), with Customer - Deliver: 2 tasks, average 4.5 6. Track the parcel: 4 of 5 (happy), with Customer and Courier 7. Receive it: 5 of 5 (delighted), with Customer and Courier **Actors: Customer, Support agent, Bank, and Courier.** **Lowest: "Confirm with the bank" at 1. Highest: "Find a product" and "Receive it" at 5.** ### Layers: Online banking platform **Diagram source** ```diagram layers "Online banking platform" desc="A customer reaches accounts, payments and cards through one gateway. Each layer calls the layer below it, and security and monitoring cut across all of them." layer "Experience" tone=blue icon=lucide:monitor { block web "Web app" sub="React" icon=simple:react block mobile "Mobile app" sub="Swift and Kotlin" icon=lucide:smartphone block gateway "API gateway" sub="Routing and limits" icon=lucide:route } layer "Domain services" tone=violet icon=lucide:boxes { block accounts "Accounts" icon=lucide:wallet block payments "Payments" icon=lucide:credit-card block ledger "Ledger" icon=lucide:book-open } layer "Data" tone=aqua icon=lucide:database { block sql "PostgreSQL" sub="Accounts and ledger" icon=simple:postgresql span=2 block cache "Redis" sub="Sessions" icon=simple:redis block events "Kafka" sub="Payment events" icon=simple:apachekafka } layer "Infrastructure" tone=neutral icon=lucide:server { block k8s "Kubernetes" sub="Three regions" icon=simple:kubernetes block net "Private network" sub="Subnets and routing" icon=lucide:cloud } aside "Cross-cutting" icon=lucide:shield { block iam "Identity" icon=lucide:key-round block audit "Audit log" icon=lucide:scroll-text block watch "Monitoring" icon=lucide:activity } gateway -> accounts : "REST" gateway -> payments : "REST" accounts -> sql : "SQL" ledger <-> sql : "SQL" payments ..> events : "payment events" sql --> k8s : "runs on" ``` *Picture: Online banking platform: 4 layers, 14 blocks, 1 aside, 6 links. A customer reaches accounts, payments and cards through one gateway. Each layer calls the layer below it, and security and monitoring cut across all of them.* **Text description** - Layer Experience (1 of 4 from the top): Web app (React), Mobile app (Swift and Kotlin), API gateway (Routing and limits). - Layer Domain services (2 of 4 from the top): Accounts, Payments, Ledger. - Layer Data (3 of 4 from the top): PostgreSQL (Accounts and ledger), Redis (Sessions), Kafka (Payment events). - Layer Infrastructure (4 of 4 from the top): Kubernetes (Three regions), Private network (Subnets and routing). - Aside Cross-cutting, beside the layers: Identity, Audit log, Monitoring. - Link: API gateway to Accounts: REST. - Link: API gateway to Payments: REST. - Link: Accounts to PostgreSQL: SQL. - Link: Ledger and PostgreSQL, both ways: SQL. - Link: Payments to Kafka: payment events, asynchronous. - Link: PostgreSQL to Kubernetes: runs on, optional or indirect. ### Matrix: Message broker options **Diagram source** ```diagram matrix "Message broker options" desc="Four message brokers compared on five criteria. Kafka is the pick for the event pipeline because it keeps order at the highest throughput." columns "Throughput" "Ordering" "Ease of ops" "Managed offer" "Typical cost" row "Kafka" icon=simple:apachekafka sub="Log-based streaming" : 5, yes, 2, partial, "Medium" row "RabbitMQ" icon=simple:rabbitmq sub="Classic queues" : 3, yes, 3, partial, "Low" row "Amazon SQS" icon=aws:sqs sub="Managed queues" : 4, no, 5, yes, "Pay per use" row "NATS" icon=simple:nats sub="Light messaging" : 4, partial, 4, no, "Low" recommend "Kafka" legend partial="Limited" ``` *Picture: Message broker options: 4 rows compared on 5 columns. Recommended: Kafka. Four message brokers compared on five criteria. Kafka is the pick for the event pipeline because it keeps order at the highest throughput.* **Text description** **Columns: Throughput, Ordering, Ease of ops, Managed offer, Typical cost.** - Kafka (Log-based streaming, recommended): Throughput: 5 of 5; Ordering: yes; Ease of ops: 2 of 5; Managed offer: Limited; Typical cost: Medium. - RabbitMQ (Classic queues): Throughput: 3 of 5; Ordering: yes; Ease of ops: 3 of 5; Managed offer: Limited; Typical cost: Low. - Amazon SQS (Managed queues): Throughput: 4 of 5; Ordering: no; Ease of ops: 5 of 5; Managed offer: yes; Typical cost: Pay per use. - NATS (Light messaging): Throughput: 4 of 5; Ordering: Limited; Ease of ops: 4 of 5; Managed offer: no; Typical cost: Low. ### Mindmap: Product launch **Diagram source** ```diagram mindmap "Product launch" desc="What the team settles before the product ships: who it is for, what it is, where to say so, and what could go wrong." root "Product launch" icon=lucide:rocket topic "Audience" icon=lucide:users { topic "Maintainers" topic "Platform teams" topic "Technical writers" topic "Agency designers" } topic "Product" icon=lucide:package { topic "Text language" topic "Layout engine" topic "Light and dark themes" topic "MCP server and API" } topic "Channels" icon=lucide:megaphone { topic "Docs and guides" topic "Launch post" topic "Forums" topic "Newsletters" } topic "Plan" icon=lucide:calendar-clock { topic "Soft launch" topic "Public beta" topic "Week 12 review" } topic "Risks" icon=lucide:triangle-alert tone=red shape=hexagon { topic "Slow first render" topic "Unclear pricing" topic "Thin documentation" } ``` *Picture: Product launch: 24 topics, 5 branches, 2 levels. What the team settles before the product ships: who it is for, what it is, where to say so, and what could go wrong.* **Text description** **Central topic: Product launch** **Branches:** - Audience - Maintainers - Platform teams - Technical writers - Agency designers - Product - Text language - Layout engine - Light and dark themes - MCP server and API - Channels - Docs and guides - Launch post - Forums - Newsletters - Plan - Soft launch - Public beta - Week 12 review - Risks - Slow first render - Unclear pricing - Thin documentation ### Pie: Where visits come from **Diagram source** ```diagram pie "Where visits come from" total="Visits in March" desc="The share of one month's visits to the docs by the channel that sent them: search brings the most, then people who typed the address." slice "Search" 18420 icon=lucide:search slice "Direct" 11960 icon=lucide:globe slice "Social" 6310 icon=lucide:share-2 slice "Email" 4180 icon=lucide:mail slice "Referral" 2140 icon=lucide:link slice "Paid" 1490 icon=lucide:megaphone ``` *Picture: Where visits come from: 6 slices of 44,500 in all, the largest Search at 42%. The share of one month's visits to the docs by the channel that sent them: search brings the most, then people who typed the address.* **Text description** **Slices, largest first:** - Search: 18,420 (42%) - Direct: 11,960 (27%) - Social: 6,310 (14%) - Email: 4,180 (9%) - Referral: 2,140 (5%) - Paid: 1,490 (3%) ### Quadrant: What to build next **Diagram source** ```diagram quadrant "What to build next" highlight=top-left desc="Fourteen roadmap ideas placed by the effort they take and the impact they have on customers. The quick wins in the top left are the ones to start with." x "Effort" low="Low effort" high="High effort" y "Impact" low="Low impact" high="High impact" zone top-left "Quick wins" tone=green icon=lucide:zap zone top-right "Big bets" tone=blue icon=lucide:rocket zone bottom-left "Fill-ins" zone bottom-right "Time sinks" tone=red icon=lucide:hourglass point "Saved filters" x=0.1 y=0.88 icon=lucide:funnel point "Dark mode" x=0.26 y=0.7 point "Keyboard shortcuts" x=0.36 y=0.56 point "Email digest" x=0.4 y=0.86 icon=lucide:mail point "Mobile app" x=0.88 y=0.9 icon=lucide:smartphone point "Public API" x=0.68 y=0.78 icon=lucide:plug point "SSO and SCIM" x=0.6 y=0.62 icon=lucide:key-round point "Offline mode" x=0.84 y=0.62 point "Release notes page" x=0.2 y=0.24 point "Footer links" x=0.4 y=0.38 point "Favicon refresh" x=0.1 y=0.1 point "Rewrite in Rust" x=0.8 y=0.14 point "Custom themes" x=0.66 y=0.4 point "Plugin marketplace" x=0.58 y=0.22 ``` *Picture: What to build next: 14 points placed by Effort and Impact. Fourteen roadmap ideas placed by the effort they take and the impact they have on customers. The quick wins in the top left are the ones to start with.* **Text description** **Horizontal axis: Effort, from Low effort to High effort.** **Vertical axis: Impact, from Low impact to High impact.** **Quick wins (top left, highlighted):** - Saved filters: Effort 0.1, Impact 0.88 - Dark mode: Effort 0.26, Impact 0.7 - Keyboard shortcuts: Effort 0.36, Impact 0.56 - Email digest: Effort 0.4, Impact 0.86 **Big bets (top right):** - Mobile app: Effort 0.88, Impact 0.9 - Public API: Effort 0.68, Impact 0.78 - SSO and SCIM: Effort 0.6, Impact 0.62 - Offline mode: Effort 0.84, Impact 0.62 **Fill-ins (bottom left):** - Release notes page: Effort 0.2, Impact 0.24 - Footer links: Effort 0.4, Impact 0.38 - Favicon refresh: Effort 0.1, Impact 0.1 **Time sinks (bottom right):** - Rewrite in Rust: Effort 0.8, Impact 0.14 - Custom themes: Effort 0.66, Impact 0.4 - Plugin marketplace: Effort 0.58, Impact 0.22 ### Sankey: Request traffic through the platform **Diagram source** ```diagram sankey "Request traffic through the platform" unit="req/s" desc="How 12,000 requests per second move from the edge through the API gateway and three services to the data stores, with blocked and failed requests branching off." node "Edge" icon=lucide:globe node "API gateway" icon=lucide:server node "Auth" icon=lucide:key-round node "Catalog" icon=lucide:book-open node "Orders" icon=lucide:package node "Redis cache" icon=simple:redis tone=aqua node "PostgreSQL" icon=simple:postgresql node "Search index" icon=lucide:search node "Blocked" icon=lucide:shield-x tone=red node "Errors" icon=lucide:circle-alert tone=red "Edge" -> "API gateway" : 10800 "Edge" -> "Blocked" : 1200 "API gateway" -> "Auth" : 3000 "API gateway" -> "Catalog" : 5200 "API gateway" -> "Orders" : 2200 "API gateway" -> "Errors" : 400 "Auth" -> "Redis cache" : 2600 "Auth" -> "PostgreSQL" : 400 "Catalog" -> "Redis cache" : 3600 "Catalog" -> "PostgreSQL" : 1200 "Catalog" -> "Search index" : 400 "Orders" -> "PostgreSQL" : 1900 "Orders" -> "Errors" : 300 ``` *Picture: Request traffic through the platform: 10 nodes and 13 flows in 4 columns, total 12,000 req/s. How 12,000 requests per second move from the edge through the API gateway and three services to the data stores, with blocked and failed requests branching off.* **Text description** - Edge to API gateway: 10,800 req/s, 90% of what leaves Edge - Edge to Blocked: 1,200 req/s, 10% of what leaves Edge - API gateway to Catalog: 5,200 req/s, 48% of what leaves API gateway - API gateway to Auth: 3,000 req/s, 28% of what leaves API gateway - API gateway to Orders: 2,200 req/s, 20% of what leaves API gateway - API gateway to Errors: 400 req/s, 4% of what leaves API gateway - Auth to Redis cache: 2,600 req/s, 87% of what leaves Auth - Auth to PostgreSQL: 400 req/s, 13% of what leaves Auth - Catalog to Redis cache: 3,600 req/s, 69% of what leaves Catalog - Catalog to PostgreSQL: 1,200 req/s, 23% of what leaves Catalog - Catalog to Search index: 400 req/s, 8% of what leaves Catalog - Orders to PostgreSQL: 1,900 req/s, 86% of what leaves Orders - Orders to Errors: 300 req/s, 14% of what leaves Orders ### Schema: Users and sessions **Diagram source** ```diagram schema "Users and sessions" desc="Each session belongs to one user." table users icon=lucide:users { id uuid pk email text unique created_at timestamptz } table sessions icon=lucide:key-round { id uuid pk user_id uuid fk expires_at timestamptz nullable } sessions.user_id -> users.id : "belongs to" ``` *Picture: Users and sessions: 2 tables, 1 relation. Each session belongs to one user.* **Text description** **Tables:** - users - id uuid (primary key) - email text (unique) - created_at timestamptz - sessions - id uuid (primary key) - user_id uuid (foreign key) - expires_at timestamptz (nullable) **Relations:** - sessions.user_id references users.id (many to one): belongs to ### Sequence: Token refresh **Diagram source** ```diagram sequence "Token refresh" desc="A client refreshes an expired access token, or is told to sign in again." actor app client "App" actor idp identity "Auth0" sub="Identity provider" app -> idp : "Refresh token" note over idp : "Checks the refresh token has not been revoked" alt "token valid" { idp --> app : "New access token" } else "token revoked" { idp --> app : "401 sign in again" } ``` *Picture: Token refresh: 2 participants. A client refreshes an expired access token, or is told to sign in again.* **Text description** **Participants:** - App, client - Auth0 (Identity provider), identity **Steps:** - 1. App to Auth0: Refresh token - Note over Auth0: Checks the refresh token has not been revoked - alt: token valid - 2. Auth0 to App (dashed): New access token - else: token revoked - 3. Auth0 to App (dashed): 401 sign in again ### State: Order lifecycle **Diagram source** ```diagram state "Order lifecycle" desc="An order is paid, checked against stock, packed and delivered, or cancelled." state pending "Pending" sub="Waiting for payment" tone=amber icon=status:pending choice stock "In stock?" state fulfilment "Fulfilment" icon=lucide:package { start -> picking state picking "Picking" tone=blue icon=lucide:clipboard-list state packed "Packed" tone=blue icon=lucide:package-check picking -> packed : "items scanned" packed -> end } state delivered "Delivered" tone=green icon=lucide:map-pin state cancelled "Cancelled" sub="Money refunded" tone=red icon=status:error start -> pending pending -> stock : "pay [card valid] / charge card" pending -> cancelled : "timeout [after 24 hours]" stock -> fulfilment : "[in stock] / reserve items" stock -> cancelled : "[sold out] / refund" fulfilment -> delivered : "arrived" delivered -> end cancelled -> end note cancelled : "Refunds stay open for 30 days" ``` *Picture: Order lifecycle: 6 states (1 composite), 11 transitions. An order is paid, checked against stock, packed and delivered, or cancelled.* **Text description** **States:** - Start - Pending (Waiting for payment), state - In stock?, choice - Fulfilment, composite state - Start of Fulfilment - Picking, state - Packed, state - End of Fulfilment - Delivered, state - Cancelled (Money refunded), state - End **Transitions:** - Start of Fulfilment to Picking - Picking to Packed on items scanned - Packed to End of Fulfilment - Start to Pending - Pending to In stock? on pay, if card valid, then charge card - Pending to Cancelled on timeout, if after 24 hours - In stock? to Fulfilment, if in stock, then reserve items - In stock? to Cancelled, if sold out, then refund - Fulfilment to Delivered on arrived - Delivered to End - Cancelled to End **Notes:** - On Cancelled: Refunds stay open for 30 days ### Swimlane: Order fulfilment **Diagram source** ```diagram swimlane "Order fulfilment" desc="A customer's order is checked against stock, paid for, packed and shipped, or cancelled." lane customer "Customer" icon=lucide:user { start placed "Order placed" icon=lucide:receipt end cancelled "Order cancelled" tone=red icon=status:error step card "Update card" sub="Enters a new card" icon=lucide:credit-card end delivered "Delivered" tone=green icon=status:success } lane web "Web app" icon=lucide:globe { step check "Check stock" icon=lucide:boxes decision stock "In stock?" step error "Show error" sub="Payment declined" tone=amber icon=lucide:triangle-alert } lane pay "Payments" icon=lucide:wallet { step charge "Charge card" icon=lucide:credit-card } lane warehouse "Warehouse" icon=lucide:warehouse { step pack "Pack order" icon=lucide:package-check step ship "Ship order" icon=lucide:send } placed -> check check -> stock stock -> charge : "[in stock]" stock -> cancelled : "[sold out]" charge -> pack : "[paid]" charge -> error : "[declined]" error -> card card -> charge : "retry" pack -> ship ship -> delivered : "arrived" ``` *Picture: Order fulfilment: 4 lanes, 10 steps, 10 transitions. A customer's order is checked against stock, paid for, packed and shipped, or cancelled.* **Text description** **Lanes:** - Customer - Order placed, start - Order cancelled, end - Update card (Enters a new card), step - Delivered, end - Web app - Check stock, step - In stock?, decision - Show error (Payment declined), step - Payments - Charge card, step - Warehouse - Pack order, step - Ship order, step **Transitions:** - Order placed to Check stock - Check stock to In stock? - In stock? to Charge card, if in stock - In stock? to Order cancelled, if sold out - Charge card to Pack order, if paid - Charge card to Show error, if declined - Show error to Update card - Update card to Charge card on retry - Pack order to Ship order - Ship order to Delivered on arrived ### Timeline: Orbit product history **Diagram source** ```diagram timeline "Orbit product history" desc="From a weekend prototype to a platform that teams run their work on." section "Prototype" { at "2019" : "Weekend hack" sub="Two founders and one laptop" icon=lucide:laptop, "First paying customer" icon=lucide:badge-check at "2020" : "Public beta" sub="1,200 sign-ups in a week" icon=lucide:rocket } section "Growth" { period "2021" { event "Seed round" sub="Led by two angels" icon=lucide:wallet event "Team of ten" icon=lucide:users } at "2022" : "Series A", "Berlin office opens" icon=lucide:map-pin } section "Platform" { at "2023" : "Public API" sub="REST and webhooks" icon=lucide:code at "2024" : "10,000 teams" tone=green icon=lucide:crown, "SOC 2 report" icon=lucide:shield-check } ``` *Picture: Orbit product history: 6 periods and 10 events in 3 sections. From a weekend prototype to a platform that teams run their work on.* **Text description** **Periods, in the order drawn:** **Section Prototype:** - 2019: Weekend hack (Two founders and one laptop); First paying customer - 2020: Public beta (1,200 sign-ups in a week) **Section Growth:** - 2021: Seed round (Led by two angels); Team of ten - 2022: Series A; Berlin office opens **Section Platform:** - 2023: Public API (REST and webhooks) - 2024: 10,000 teams; SOC 2 report ### Tree: storefront **Diagram source** ```diagram tree "storefront" desc="The layout of a small repository: services, libraries, and the file a change touches." dir "services" note="Deployable services" icon=lucide:layers { dir "api" note="REST API, a container service" icon=lucide:server { file "service.toml" note="Service config and bindings" file "index.ts" note="Hono routes" change=modified size="4 KB" } dir "docs" note="Documentation site" icon=lucide:book-open dir "site" note="Homepage" icon=lucide:globe } dir "libs" note="Shared code" icon=lucide:package { dir "engine" note="Parser, layout, renderer" { dir "src" { dir "kinds" note="One folder for each diagram kind" file "index.ts" note="Public API" } file "manifest.json" } dir "ui" note="Design system" } dir "scripts" note="Doc generators" file "README.md" note="Start here" mark=on file "tsconfig.json" file "pnpm-lock.yaml" size="412 KB" omitted 9 "files" ``` *Picture: storefront: file tree of 17 entries (10 folders, 7 files), 4 levels, 1 changed, 9 more not drawn. The layout of a small repository: services, libraries, and the file a change touches.* **Text description** - services/ (folder, 3 entries): Deployable services - services/api/ (folder, 2 entries): REST API, a container service - services/api/service.toml: Service config and bindings - services/api/index.ts (modified, 4 KB): Hono routes - services/docs/ (folder): Documentation site - services/site/ (folder): Homepage - libs/ (folder, 2 entries): Shared code - libs/engine/ (folder, 2 entries): Parser, layout, renderer - libs/engine/src/ (folder, 2 entries) - libs/engine/src/kinds/ (folder): One folder for each diagram kind - libs/engine/src/index.ts: Public API - libs/engine/manifest.json - libs/ui/ (folder): Design system - scripts/ (folder): Doc generators - README.md (emphasised): Start here - tsconfig.json - pnpm-lock.yaml (412 KB) - and 9 more files ### Treemap: Monthly cloud spend **Diagram source** ```diagram treemap "Monthly cloud spend" unit="$" better=down desc="Where the monthly cloud bill goes, by service, with the change since last month. A rise in cost is drawn red." group "Compute" icon=lucide:cpu { item "EC2" 18400 icon=aws:ec2 delta="8%" item "Fargate" 6800 icon=aws:fargate delta="21%" item "Lambda" 4200 icon=aws:lambda delta="-12%" item "EKS" 3100 icon=aws:eks } group "Data" icon=lucide:database { item "RDS" 9600 icon=aws:rds delta="4%" item "S3" 5200 icon=aws:s3 delta="-3%" item "DynamoDB" 3900 icon=aws:dynamodb item "OpenSearch" 3400 icon=aws:opensearch item "ElastiCache" 2800 } group "Network" icon=lucide:globe { item "Data transfer" 4800 delta="15%" item "NAT gateway" 3300 icon=aws:nat-gateway item "CloudFront" 2200 icon=aws:cloudfront delta="-6%" item "Load balancers" 1900 icon=aws:elb } group "Operations" icon=lucide:activity { item "CloudWatch" 4600 icon=aws:cloudwatch item "Support plan" 3200 group "Security" icon=lucide:shield { item "GuardDuty" 2400 icon=aws:guardduty item "WAF" 1900 icon=aws:waf } } ``` *Picture: Monthly cloud spend: 17 items in 5 groups, total $81,700. Where the monthly cloud bill goes, by service, with the change since last month. A rise in cost is drawn red.* **Text description** - Group Compute: $32,500, 40% of the total - EC2: $18,400, 23% of the total, up 8% - Fargate: $6,800, 8% of the total, up 21% - Lambda: $4,200, 5% of the total, down 12% - EKS: $3,100, 4% of the total - Group Data: $24,900, 30% of the total - RDS: $9,600, 12% of the total, up 4% - S3: $5,200, 6% of the total, down 3% - DynamoDB: $3,900, 5% of the total - OpenSearch: $3,400, 4% of the total - ElastiCache: $2,800, 3% of the total - Group Network: $12,200, 15% of the total - Data transfer: $4,800, 6% of the total, up 15% - NAT gateway: $3,300, 4% of the total - CloudFront: $2,200, 3% of the total, down 6% - Load balancers: $1,900, 2% of the total - Group Operations: $12,100, 15% of the total - CloudWatch: $4,600, 6% of the total - Group Security: $4,300, 5% of the total - GuardDuty: $2,400, 3% of the total - WAF: $1,900, 2% of the total - Support plan: $3,200, 4% of the total ### Xychart: API latency against the SLO **Diagram source** ```diagram xychart "API latency against the SLO" desc="Weekly p50 and p95 latency of the checkout API against its 400 ms objective: p95 crossed the line after release 2.3 and came back under it after the cache fix." x "Week" ["W1" "W2" "W3" "W4" "W5" "W6" "W7" "W8"] y "Latency" unit="ms" min=0 max=500 bar "p95" [310 330 345 420 455 440 350 335] line "p50" [120 125 128 160 170 165 135 130] threshold "SLO 400 ms" 400 tone=red note "Release 2.3" at="W4" note "Cache fix" at="W7" ``` *Picture: API latency against the SLO: 2 series (bar and line) over 8 places on the Week axis, on a scale of 0 ms to 500 ms. Weekly p50 and p95 latency of the checkout API against its 400 ms objective: p95 crossed the line after release 2.3 and came back under it after the cache fix.* **Text description** **X axis, Week: W1, W2, W3, W4, W5, W6, W7, W8.** **Y axis, Latency: 0 ms to 500 ms.** **Drawn with the places along the bottom.** **Series:** - p95 (bar): W1 310 ms, W2 330 ms, W3 345 ms, W4 420 ms, W5 455 ms, W6 440 ms, W7 350 ms, W8 335 ms - p50 (line): W1 120 ms, W2 125 ms, W3 128 ms, W4 160 ms, W5 170 ms, W6 165 ms, W7 135 ms, W8 130 ms **Thresholds:** - SLO 400 ms: 400 ms - Crossed above SLO 400 ms: p95 in W4, W5, and W6 **Notes:** - W4: Release 2.3 - W7: Cache fix ## Statements by kind ### Git graph Branches as lanes, with the commits, merges, and tags along them, for a repository history. The header line is `git "Title"`. | Statement | What it does | Example | |---|---|---| | `git "<title>" direction=<auto\|right\|down> main=<name> desc="<summary>"` | The header, and the first line. Every option is optional. direction=auto, the default, draws one lane per row with time running left to right when that fits a column of the page, and one lane per column with time running down the page when it does not. direction=right and direction=down fix it. main= names the default branch, which is main unless you say otherwise. | `git "Release flow" direction=right main=trunk desc="A feature branch merges into the trunk and the release is tagged."` | | `commit "<message>" id=<id> tag="<name>" type=<normal\|highlight\|reverse>` | A commit on the current branch. Statements are written in time order: the order written is the order in time. Every part after the word is optional, and a commit with no message is a bare dot. id= names the commit for cherry-pick and from=; an id is drawn only on a commit that a cherry-pick copies. tag= puts a tag on the commit; repeat it or list names with commas, up to 3. type=highlight draws a milestone with a halo, and type=reverse a ring with a cross for a revert. Valid types: normal, highlight, reverse. | `commit "Add search" id=c2 tag="v0.2" type=highlight` | | `branch <name> from=<branch or id> order=<n> tone=<tone>` | Makes a branch at the newest commit of the current branch, or of from=, and switches to it. A name with a slash goes in double quotes: branch "feature/search". from= takes a branch name or a commit id, so a hotfix can start from an old release. order= moves the lane: the lowest order is the first lane, a branch with no order takes the number of branches made before it, and order=1 is the second lane. tone= sets the lane color. A branch needs a commit to start from, so write a commit first. | `branch "feature/search" from=develop order=2` | | `checkout <branch>` | Switches the current branch, so the commits that follow go on it. switch is another spelling. The branch must already exist. | `checkout develop` | | `merge <branch> "<message>" id=<id> tag="<name>" type=<type>` | A merge commit on the current branch that brings in the newest commit of another branch, drawn with a ring in the color of the merged branch and a line from that branch. The message and the options are optional. A branch cannot be merged into itself, and merging one that is already part of the current branch adds nothing, which draws warning W300. | `merge develop "Release 1.0" tag="v1.0" type=highlight` | | `cherry-pick <id> "<message>" tag="<name>"` | A copy of one commit, made on the current branch and drawn with a dashed ring in the color of the lane it came from. The commit is named by its id=, so give the source commit an id. With no message the copy keeps the message of the commit it copies. A commit the current branch already has cannot be picked. | `cherry-pick c3 tag="v1.0.1"` | | `desc "<summary>"` | The plain-language summary, as a statement of its own, for people who prefer it to desc= on the header. | `desc "Hotfix 1.0.1 is picked back onto main."` | ### Gantt chart Tasks and milestones on a time axis, in sections and with dependencies, for a project schedule. The header line is `gantt "Title"`. | Statement | What it does | Example | |---|---|---| | `gantt "Title" start=<date> end=<date> unit=<day\|week\|month\|quarter\|year> today=<date> weekends=<show\|shade\|skip> holidays=<dates> desc="<text>"` | The header. Every option is optional. start= and end= can only widen the axis, never cut a task. unit= is the step of the axis (the default follows the span of the plan). today= draws a marker on that day, and there is none without it. weekends=shade tints Saturdays and Sundays, and weekends=skip also leaves them out when counting durations in days and weeks. holidays= is a comma-separated list of days off that are shaded and not counted. Dates are YYYY-MM-DD. | `gantt "Launch plan" start=2026-01-05 unit=week today=2026-01-26 weekends=skip holidays=2026-04-03` | | `section "<Name>" tone=<tone> {` | Opens a band of rows with its name in a tab on the left. Put tasks and milestones inside and close it with } on its own line. Sections do not nest. tone= colors the band and the bars inside it. | `section "Design" tone=violet {` | | `task <id> "<Label>" start=<date> \| after=<ids> duration=<n>d\|<n>w\|<n>mo \| end=<date> status=<status> tone=<tone>` | A bar on the axis. Say when it starts with start= (a day) or after= (one or more ids, a comma-separated list: it starts the day after the last of them ends), and how long it lasts with duration= (days, weeks, or calendar months) or end= (its last day). status= is planned (the default), active, done, or critical, and critical can be added to another: status=critical,done. The label goes inside the bar when it fits and beside it when it does not. | `task ui "UI design" after=ux duration=2w status=active` | | `milestone <id> "<Label>" at=<date> \| after=<ids> status=<status> tone=<tone>` | A point in time drawn as a diamond: at= a day, or after= tasks, where the last of them ends. A milestone has no duration and no end. status= is planned, done, or critical. | `milestone beta "Beta" at=2026-03-02` | | `desc "<text>"` | The plain-language description of the chart, for assistive technology; the same as desc= on the header. | `desc "A twelve week plan from research to launch."` | ### Class diagram Classes with their members and the relations between them, for object models. The header line is `class "Title"`. | Statement | What it does | Example | |---|---|---| | `class <id> ["Label"] [type=<type>] [stereotype="<word>"] [tone=<tone>] [{]` | Declares a class, shown as a box. The label is the name in the header and may hold type parameters, such as "Repository<T>". type is class (a concrete class. the default), abstract (a class that cannot be created itself, only extended), interface (a contract of methods that other classes implement), enum (a fixed set of named values; each member line is one value). Without a { the class has no members; with one, list them on the lines that follow and close with } on its own line. | `class Order "Order" type=class stereotype="entity" {` | | `"<mark><name>[: <type>]" [static] [abstract]` | A member of the open class, one per line, the whole text in double quotes. The mark says who sees it: + public, - private, # protected, ~ package. A member with a parenthesis is a method and the rest are attributes; attributes are drawn first. After the colon comes the type. static draws it underlined and abstract in italics. On an enum, each member is one value. | `"+place(order: Order): void" abstract` | | `package <id> ["Label"] [tone=<tone>] {` | A frame around classes, with its name in a corner tab. Packages nest up to 3 deep. Close with } on its own line. | `package billing "Billing" tone=violet {` | | `<from> ["<count>"] <relation> ["<count>"] <to> [: "label"]` | Relates two classes. The relation is one of: extends inheritance, implements realization, owns composition, has aggregation, -> association, <-> bidirectional, --> dependency, -- link. A count is the cardinality at that end, up to 12 characters, such as "1", "0..1" or "*". Write inheritance and implementation from the child to the parent, and composition and aggregation from the whole to the part. | `Customer "1" -> "*" Order : "places"` | | `note on <class> : "text"` | A note card joined to a class by a dotted line. Write note : "text" for a note that stands alone. | `note on Order : "Totals are in cents."` | | `desc "text"` | The plain-language description of the diagram, for readers who cannot see it. The header option desc="<text>" does the same. direction=down\|right\|up\|left on the header sets the reading direction; down, the default, puts parents above their children. | `desc "How an order reaches a customer."` | ### State States and the transitions between them, with composite states, choices, and parallel regions, for lifecycles and state machines. The header line is `state "Title"`. | Statement | What it does | Example | |---|---|---| | `state <id> "Label" sub="<description>" tone=<tone> icon=<set:name>` | Adds one state: a rounded card with its name. sub= is the line or lines under the name, and a \n starts a new line. icon= puts a chip with that icon before the name, and a card with no icon has no chip. A tone other than neutral tints the card, and green, amber, or red also draw every transition that arrives at the state in a softer line of that tone, so the way to a good outcome and the way to a bad one part by color. The other tones never color a line. The ids start and end are taken by the markers. | `state paid "Paid" sub="Money received" tone=green` | | `state <id> "Label" {` | Opens a composite state: a state that holds other states, drawn as a frame with its name, and its icon when it has one, in a corner tab. A frame inside a frame is shaded a step stronger. Close it with } on its own line. Inside, start and end are the markers of this state alone, and a transition may enter or leave the whole state by its id. | `state shipping "Shipping" {` | | `from -> to : "event [guard] / action"` | Adds a transition. The label is optional, and each of its parts is optional too: the event that triggers it, a guard in square brackets that must hold, and an action after a slash that runs. The picture sets them apart: the event is plain text, the guard is a tag with a branch glyph, and the action follows a bolt glyph, so the brackets and the slash are not drawn. Only -> draws a transition. start and end are the markers of the scope the line is in: start only begins a transition and end only finishes one. | `pending -> paid : "payment received [card valid] / charge card"` | | `start -> <id>` | The start marker of the scope: a filled dot where the lifecycle begins, at the top level of the diagram or inside a composite state. It needs no declaration. | `start -> pending` | | `<id> -> end` | The end marker of the scope: a ring with a dot, where the lifecycle finishes. It needs no declaration, and several transitions may lead to it. | `delivered -> end` | | `choice <id> "Question?" tone=<tone> icon=<set:name>` | A pill where one path splits into several, each taken on a condition: a tinted pill with a branch icon in a round chip and the question beside it, a sibling of the state cards. icon= swaps the icon and icon=none leaves the chip out. The question is optional: with none the choice is a small circle holding only the icon, and its branches carry the conditions as guards. Its default tone is amber. | `choice stock "In stock?"` | | `fork <id>` | A bar where one path splits into parallel paths. It grows long enough to hold the transitions that meet it. | `fork split` | | `join <id>` | A bar where parallel paths merge into one. | `join merge` | | `history <id> deep=on` | A circle marked H inside a composite state: entering it returns to the state that was active last. deep=on remembers the whole depth of the nesting and draws H*. | `history last deep=on` | | `region "Name" {` | Opens a region inside a composite state: one of the parts that run side by side. A composite state with regions holds only regions, and each region has its own start and end markers. A dashed line separates the regions. The name is optional. | `region "Reader" {` | | `note <id> : "text" side=<left\|right\|top\|bottom>` | Attaches a note to a state, a choice, or a composite state. A note sits across the reading direction, so a transition never meets it: left and right become top and bottom in a diagram that reads sideways, and the other way round in one that reads up or down. A note on a composite state sits inside its frame. With no side, it goes below or to the right. | `note paid : "Refunds are allowed for 30 days" side=right` | | `desc "<text>"` | A plain-language description of the diagram for the text description, as desc= on the header does. | `desc "How an order moves from payment to delivery."` | ### Timeline Dated events on a rail, in the order written and grouped in sections, for a history or a roadmap. The header line is `timeline "Title"`. | Statement | What it does | Example | |---|---|---| | `timeline "Title" direction=right\|down side=both\|one desc="<text>"` | The header. direction=right (the default) draws a horizontal rail read left to right, direction=down a vertical one read top to bottom. side=both (the default) alternates the cards between the two sides of the rail, side=one puts them all on the same side: below the rail, or to its right. Periods are drawn in the order written, never sorted. | `timeline "Product history" direction=down side=one` | | `section "Name" tone=<tone> icon=<ref> {` | Opens an era: a tinted frame, with its name in a corner tab, around the periods written before the closing } on its own line. A section takes the next tone of blue, violet, aqua, orange, green, amber unless tone= names another. Sections do not nest. A period written outside every section sits on the rail with no frame. | `section "Early days" tone=violet {` | | `at "<period>" tone=<tone> : "<event>" sub="<text>" icon=<ref> tone=<tone>, "<event>"` | A period and its events on one line. The period is a milestone on the rail and each event is a card hung from it, the first nearest the rail. Events are separated by commas, and each may take sub= (a quieter second line), icon=, and tone=. A period is a label, not a date: "2019", "Q3 2019", "Spring", and "v2.0" all work. At least one event is expected; a period with none draws a milestone alone and a warning. | `at "2019" : "Founded", "First customer" sub="A design studio in Lyon" icon=lucide:flag` | | `period "<period>" tone=<tone> {` | The same as at, with one event on each line, for a period whose events carry sub= and icon=. Write event lines inside it and close it with } on its own line. | `period "2020" tone=green {` | | `event "<event>" sub="<text>" icon=<ref> tone=<tone>` | One card of the period block it is written in. Only a period block holds events; outside one the statement is error E381. | `event "Seed round" sub="Led by two angels" icon=lucide:wallet` | | `desc "<text>"` | One or two sentences describing the timeline, for the text description. The same as desc= on the header. | `desc "How the product grew from a studio to a platform."` | ### User journey The steps a person takes and how they feel at each, as a satisfaction curve over stages of tasks and the actors involved. The header line is `journey "Title"`. | Statement | What it does | Example | |---|---|---| | `journey "Title" desc="<text>" marks=faces\|dots legend=on\|off` | The header. marks=faces (the default) draws a face from sad to happy on the curve, and marks=dots draws the score as a number in a dot. legend=off hides the legend of the scale and the actors. A journey may hold at most 100 tasks, 25 stages, and 24 actors, and a task may name at most 8 actors; past that it is refused with E140. | `journey "Checkout experience" desc="How a shopper feels from search to payment" marks=faces` | | `section "Stage name" tone=<tone> {` | A stage of the journey: a named run of tasks, drawn as a frame with a tab, one after another from left to right. It needs at least one task. tone= tints the frame and is optional. Tasks written outside any section are drawn with no frame. | `section "Browse" {` | | `task "Name" score=<1-5> actors="<Actor>, <Actor>" sub="<second line>"` | A step the person takes, with how they feel at it: score 1 is frustrated, 3 is neutral, and 5 is delighted. The score is a whole number and the only required option. actors= lists who is involved, separated by commas, each as a chip of initials; sub= adds a second line under the name. Tasks run left to right in the order written, and a smooth curve joins their scores. | `task "Find a product" score=5 actors="Customer"` | | `actor "Name" tone=<tone>` | Declares an actor so its color is chosen, and so a task that names someone else is an error (a typo). It is optional: with no actor statement the actors are the names that the tasks use, in the order they appear. Without tone= an actor takes the next of blue, violet, neutral, aqua, orange, amber, green, red. Two spellings that differ only in case or spacing are one actor. | `actor "Customer" tone=blue` | | `desc "text"` | One or two sentences describing the journey, for the text description. The same as desc= on the header. | `desc "A shopper finds a product, pays, and waits for the parcel."` | ### Mind map A root idea and the branches that grow from it, drawn round the root or as a tidy tree, for brainstorms and outlines. The header line is `mindmap "Title"`. | Statement | What it does | Example | |---|---|---| | `mindmap "<title>" layout=<layout> desc="<text>"` | The header; title, layout=, and desc= are optional, and with no title the root names the picture. layout= is auto, radial, right, or down: auto draws radial when the map has two branches or more and fits a page column, and right otherwise; radial puts the root in the middle with its branches on both sides; right and down are tidy trees. | `mindmap "Product strategy" layout=radial` | | `root "<label>" icon=<set:name> tone=<tone> shape=<shape>` | The central idea, written once, directly under the header. Every other topic branches from it. It is a circle in the ink colour unless it says otherwise. | `root "Product strategy" icon=lucide:target` | | `topic "<label>" icon=<set:name> tone=<tone> shape=<shape>` | A branch of the root when written at the left margin, and of the topic whose block it sits in otherwise. Branches take blue, orange, aqua, violet, amber, green, and red in turn and keep the tone down the branch; tone= overrides it. shape= is pill, card, circle, or hexagon, and pill is the default. Topics have no ids. | `topic "Growth" tone=blue icon=lucide:users` | | `topic "<label>" {` | A topic with branches of its own: end the line with {, write its topics on the lines after, and close with } on its own line. Blocks nest to 10 levels. Indentation means nothing. | `topic "Retention" {` | | `}` | Closes the nearest block that is still open, on a line of its own. | `}` | ### Treemap Nested areas sized by value, grouped by branch, for where size or cost sits in a hierarchy. The header line is `treemap "Title"`. | Statement | What it does | Example | |---|---|---| | `treemap "Title" unit="<unit>" sort=<desc\|written> values=<on\|off> better=<up\|down> desc="<text>"` | The header. Every option is optional. unit= says what the values count and is drawn after each value (unit="MB", unit="%"; a currency symbol such as "$" leads). sort=desc (the default) draws the largest tile first, which packs best; sort=written keeps the order written. values=off hides the values on the tiles. better= says which way of a change is good news and so drawn green: better=up (the default) for sizes and coverage, better=down for cost and errors. | `treemap "Cloud spend" unit="$" better=down` | | `group "<Name>" tone=<tone> icon=<icon> {` | Opens a frame sized by what it holds, with its name, icon, and total in a corner tab. Put items and groups inside and close it with } on its own line. Groups nest up to 4 deep. Each top-level branch takes the next tone of a rotation, and what is inside keeps it; tone= changes it. The theme chooses every color. | `group "Compute" tone=blue icon=lucide:server {` | | `item "<Name>" <value> tone=<tone> icon=<icon> delta=<change>` | A tile whose area is its value. The value is a number above zero, written right after the name with no quotes and no unit. delta= draws a chip with an arrow and the size of a change since the last period, in the unit of the values or in percent: delta=5 for a rise, delta="-5" for a fall (a sign needs quotes), delta="-12%" for a percent. A tile too small for its label shows no label; the full list is in the text description. | `item "EC2" 12400 icon=lucide:server delta="8%"` | | `desc "<text>"` | The plain-language description of the treemap, for assistive technology; the same as desc= on the header. | `desc "Where the monthly cloud bill goes, by service."` | ### Share chart Parts of a whole drawn as a donut with a legend, for a few shares that add up to one total. The header line is `pie "Title"`. | Statement | What it does | Example | |---|---|---| | `pie "<title>" unit=<text> total=<text> sort=<order> values=<on\|off> fold=<n> labels=<place> desc="<text>"` | The header; every option is optional. unit= is written after each number ("%", "ms", "visits") or before it for a currency sign ("$"). total= is the caption under the total in the hole of the ring (the default is Total). sort= is desc or written: desc draws the largest first, clockwise from the top, with Other last. values=off prints the share and not the number. fold=5 draws at most 5 slices and merges the smallest into one Other. labels= is outside or legend: outside names each slice beside the ring on a leader line, and legend names them all in a legend under it. | `pie "Where visits come from" unit=" visits" total="Visits" fold=5` | | `slice "<name>" <number> tone=<tone> icon=<set:name>` | One share of the whole, written as its name and then its number: zero or more, plain digits with an optional fraction, no sign, comma, or percent sign. The ring scales the numbers to a full turn, so they need not add up to 100. Slices take blue, orange, aqua, violet, amber, green, red, and neutral in turn; tone= chooses one, and a slice named Other is grey. icon= puts an icon in the slice's label. Names are unique, in any case, and slices have no ids. | `slice "Search" 28 tone=aqua icon=lucide:search` | | `desc "<text>"` | The plain-language description of the chart, for assistive technology; the same as desc= on the header. | `desc "Where the visits of one month came from."` | ### Quadrant chart Items placed on two axes and read by quadrant, for priorities such as impact against effort. The header line is `quadrant "Title"`. | Statement | What it does | Example | |---|---|---| | `quadrant "Title" highlight=<zone> desc="<text>"` | The header. Both options are optional. highlight= names the zone to act on (top-right, top-left, bottom-left, or bottom-right) and gives it a stronger wash and an outline. | `quadrant "Where to invest" highlight=top-left` | | `x "<Title>" low="<text>" high="<text>"` | The horizontal axis: low= is the word at the left end (the value 0) and high= the word at the right end (the value 1). With a title and no ends they read Low and High. Write at least one of the three. | `x "Effort" low="Easy" high="Hard"` | | `y "<Title>" low="<text>" high="<text>"` | The vertical axis, the same way: low= is the bottom (0) and high= the top (1). | `y "Impact" low="Small" high="Large"` | | `zone <position> "<Name>" tone=<tone> icon=<icon>` | Names one of the four zones in a tab in its corner. Position is top-right, top-left, bottom-left, or bottom-right. tone= washes the zone, and its points take that tone unless they choose their own. | `zone top-left "Quick wins" tone=green icon=lucide:zap` | | `point "<Label>" x=<0 to 1> y=<0 to 1> tone=<tone> icon=<icon>` | An item. x runs across and y runs up, both from 0 to 1 (write 0.8, not 80). 0.5 is the dashed line and belongs to the high side. icon= draws a chip and no icon draws a dot. Labels are unique. | `point "Public API" x=0.7 y=0.8 icon=lucide:plug` | | `desc "<text>"` | The plain-language description of the chart, for assistive technology; the same as desc= on the header. | `desc "Where each idea falls on effort and impact."` | ### Layered architecture Stacked layers of blocks with the links between them, for layered and tiered architectures. The header line is `layers "Title"`. | Statement | What it does | Example | |---|---|---| | `layers "Title" direction=<down\|up> counts=<on\|off> desc="<text>"` | The header. Every option is optional. direction=down (the default) puts the first layer on top; direction=up puts it at the bottom, for a stack built on its foundation. counts=on shows how many blocks each layer holds in its tab. | `layers "Online banking" direction=down` | | `layer "<Name>" tone=<tone> icon=<icon> {` | Opens a wide frame with its name in a corner tab. Write blocks inside and close it with } on its own line. Layers stack in the order written, up to 12. Each takes the next tone of a rotation unless tone= sets one, and its blocks keep the tone. The blocks share one row and wrap to more rows when they cannot keep their words whole. The theme chooses every color. | `layer "Domain" tone=violet icon=lucide:boxes {` | | `block <id> "<Label>" sub="<note>" icon=<icon> tone=<tone> span=<1-6>` | A card in a layer or an aside. The id is what links name. sub= is a short note under the label, such as the technology. span= makes the card as wide as that many columns of its layer, so a database can be wider than a service. At most 80 blocks in all. | `block pay "Payments" sub="Go" icon=lucide:credit-card span=2` | | `aside "<Name>" tone=<tone> icon=<icon> {` | A tall band beside the stack for what cuts across every layer, such as security or monitoring, with its own blocks stacked in it. Up to 2 asides. Blocks in an aside take no links, because the aside applies to every layer. | `aside "Security" icon=lucide:shield {` | | `<from> -> <to> : "<label>"` | A link between two blocks of the layers. Arrows: -> a call, --> a reply or an optional link (dashed), <-> two ways, ..> asynchronous (dotted), -- no direction. The line leaves and enters the top and bottom of a block and never crosses a block. Keep the few links that show how the layers depend on each other. | `web -> pay : "REST"` | | `desc "<text>"` | The plain-language description of the diagram, for assistive technology; the same as desc= on the header. | `desc "Each layer calls the layer below it."` | ### C4 diagram People, systems, containers, and components with their relationships and boundaries, in the C4 model of system context, container, component, deployment, and dynamic views. The header line is `c4 "Title"`. | Statement | What it does | Example | |---|---|---| | `person\|system\|container\|component\|db <id> "Name" desc="<what it does>" tech="<technology>" external=on icon=<set:name> tone=<tone>` | Adds one element: a card with its name, a caption that says what it is ([Person], [Software System], [Container: Spring Boot]), and its description. person is a user, system a software system, container an application or a data store that runs on its own, component a part of a container, and db a database. tech= is the technology and shows in the caption. external=on marks a person or a system outside the scope of the picture: a plain card with a dashed outline, while everything inside the scope is tinted. Each type has its own icon and tone, and icon= and tone= replace them. | `container api "API Application" tech="Spring Boot" desc="Serves the banking data as JSON."` | | `boundary <id> "Name" kind=system\|container\|enterprise\|deployment\|group tech="<technology>" {` | Opens a boundary: a frame with its name and a caption such as [Software System] in a corner tab, around the elements written inside it. Boundaries nest, up to four deep, and close with } on a line of its own. A deployment boundary is a solid frame with a server icon, and tech= is its technology, such as Ubuntu 22.04. | `boundary bank "Internet Banking System" kind=system {` | | `from -> to : "What it does" tech="<protocol>"` | Adds a relationship between two elements. The label and tech= are both optional; the technology is a tag at the end of the label. -> is a call, --> an asynchronous or optional one (dashed), ..> an event (dotted), and <-> or <--> go both ways. In a view=dynamic diagram the lines are numbered in the order written. | `spa -> api : "Makes API calls to" tech="JSON/HTTPS"` | | `desc "<text>"` | A plain-language description of the diagram for the text description, as desc= on the header does. | `desc "How the customer reaches the banking system."` | ### Matrix Rows against columns with meaningful cells, for options comparisons, decision matrices, RACI charts, and risk heat maps. The header line is `matrix "Title"`. | Statement | What it does | Example | |---|---|---| | `matrix "Title" style=<style> total=<on\|off> desc="<text>"` | The header. style= is compare (the default: options against criteria), score (a decision matrix, total on), raci (who is Responsible, Accountable, Consulted, Informed), or risk (a likelihood against impact grid). total=on adds a weighted total column. | `matrix "Message brokers" style=score` | | `columns "<Name>" weight=<1 to 9> icon=<icon> "<Name>" "<Name>"` | The columns of compare, score, and raci: criteria, or roles in a RACI chart. Each quoted name may be followed by its own weight= (how much it counts in the total, default 1) and icon=. | `columns "Throughput" weight=3 icon=lucide:gauge "Ordering" "Cost"` | | `row "<Name>" icon=<icon> tone=<tone> sub="<text>" : <cell>, <cell>` | A row with one cell for each column after the colon, separated by commas. A cell is yes, no, partial, na, a score from 0 to 5 (drawn as dots), or "quoted text". In style=raci a cell is R, A, C, I, a pair such as "A R", or na. | `row "Kafka" icon=simple:apachekafka sub="Log-based" : 5, yes, "Medium"` | | `recommend "<Row name>"` | Marks one row as the pick, with a tone wash, a stronger outline, and a Recommended tab. It is never chosen from the totals for you. | `recommend "Kafka"` | | `legend yes="<text>" partial="<text>" no="<text>" na="<text>"` | compare and score only: says what each mark means, drawn under the table as the same discs with their words. Only the marks named are shown, in the order written. Lint warns when a partial cell has no legend saying what partial means. | `legend yes="Allowed" partial="Own records only" no="Denied"` | | `item "<Label>" likelihood=<1 to 5> impact=<1 to 5> icon=<icon>` | style=risk only: a risk placed on the 5 by 5 grid. The cell takes a band from likelihood times impact: low, moderate, high, or critical. | `item "Data loss" likelihood=2 impact=5 icon=lucide:database` | | `levels <likelihood\|impact> "<L1>" "<L2>" "<L3>" "<L4>" "<L5>" title="<text>"` | style=risk only: names the five levels of an axis, lowest first, and optionally the axis. Without it the levels run Rare to Almost certain and Negligible to Severe. | `levels likelihood "Rare" "Unlikely" "Possible" "Likely" "Certain"` | | `desc "<text>"` | The plain-language description of the matrix, for assistive technology; the same as desc= on the header. | `desc "Four brokers scored on five criteria."` | ### Sankey diagram Flows between stages drawn as ribbons as thick as the amount, for where traffic, data, or cost goes. The header line is `sankey "Title"`. | Statement | What it does | Example | |---|---|---| | `sankey "<title>" unit="<unit>" values=<on\|off> sort=<desc\|written> align=<justify\|left> desc="<text>"` | The header; every option is optional. unit= says what the amounts count and is written after each amount (unit="req/s"), or before it for a currency sign (unit="$"). values=off hides the amounts. sort= is desc or written: it orders the first column and settles ties. align= is justify or left: justify puts every node with nothing leaving it in the last column, left leaves it beside its source. | `sankey "Request traffic" unit="req/s"` | | `"<From>" -> "<To>" : <amount> tone=<tone>` | One flow, written as the two node names, an arrow, a colon, and the amount: plain digits with an optional fraction, above zero, no sign, comma, or unit. A node exists once a flow names it, so a node has no id and its name is its identity. The ribbon is as thick as the amount and takes the tone of the node it leaves. The same pair is written once. A flow back to an earlier node is an error, because a sankey only goes forward. Nodes take blue, orange, aqua, violet, amber, green, and neutral in turn, in the order they first appear. | `"Edge" -> "API gateway" : 12000` | | `node "<Name>" icon=<set:name> tone=<tone>` | Gives a node an icon and a tone. Write it before or after the flows that name the node, once. A node line for a name no flow uses is an error. The theme chooses every color. | `node "API gateway" icon=lucide:server tone=blue` | | `desc "<text>"` | The plain-language description of the diagram, for assistive technology; the same as desc= on the header. | `desc "How requests move from the edge to the data stores."` | ### XY chart Bars, lines, and areas over a category or number axis, with reference lines, for metrics, capacity, and trends. The header line is `xychart "Title"`. | Statement | What it does | Example | |---|---|---| | `xychart "<title>" direction=<vertical\|horizontal> stack=<on\|off> values=<on\|off> desc="<text>"` | The header; every option is optional. horizontal puts the places down the side, which suits a ranking. stack=on stacks the bar series. values=off leaves the numbers off the bars. The title is in a corner tab. | `xychart "Latency by week" stack=off` | | `x "<title>" ["<place>" "<place>"] or x "<title>" <from> to <to>` | The horizontal axis, once: named places in square brackets, or a number range that every series is spread evenly over. Each series then has one value for each place. Write a negative number as -5, and text with + ( < # or a bare / in quotes. | `x "Week" ["W1" "W2" "W3" "W4"]` | | `y "<title>" unit=<text> min=<number> max=<number>` | The value axis, once; every part is optional. Ticks are on round numbers. Bars and areas start at zero unless min= says otherwise (lint warns). A word unit such as "ms" goes in the title, a symbol such as "$" or "%" on the numbers. | `y "Latency" unit="ms" min=0 max=800` | | `bar\|line\|area "<name>" [<number> <number>] tone=<tone> icon=<set:name>` | One series: its name, then one plain number for each place in square brackets. area adds a wash under its line. Series take blue, orange, aqua, violet, amber, green, red, and neutral in turn; tone= chooses one. Up to 4 series are named beside their last points, more in a legend. | `line "p95" [310 360 345 420]` | | `threshold "<label>" <number> tone=<tone> breach=<above\|below\|none>` | A labelled reference line, red unless tone= says otherwise. A bar or point on the wrong side of it (above by default) is drawn in its tone. The axis grows to hold it. | `threshold "SLO 400 ms" 400 tone=red` | | `note "<text>" at="<place>"` | A callout above the plot with a dotted line down to a place on the x axis (an x value on a number axis). Not on a horizontal chart. | `note "Release 2.3" at="W3"` | | `desc "<text>"` | The plain-language description, for assistive technology; the same as desc= on the header. | `desc "How the latency of the API moved over eight weeks."` | ### Tree A hierarchy drawn as a file tree, an org chart, or an outline, for repository layouts, teams, ownership, and taxonomies. The header line is `tree "Title"`. | Statement | What it does | Example | |---|---|---| | `tree "Title" style=<files\|org\|outline> direction=<down\|right> counts=<on\|off> desc="<text>"` | The header. style=files (the default) draws a repository layout: a rounded frame with the title in its corner tab, one row for each entry with an icon chip, the name, a quiet column of notes, and thin guide lines from a folder to its entries. style=org draws cards in a tidy tree, for teams, ownership, and service dependencies: direction=down (the default) or direction=right says which way it grows, and a wide chart lists leaves under their parent. style=outline draws a compact bulleted list for a taxonomy. counts=on adds how many entries each folder holds. Only org reads direction. | `tree "plotwork-app" style=files` | | `dir "<Name>" note="<text>" icon=<icon> tone=<tone> change=<added\|modified\|removed> size="<size>" mark=<on\|off> {` | A folder, a team, or a group. Write its entries inside and close with } on its own line; with no { it is an empty or collapsed folder. Entries nest up to 8 levels. note= is the quiet text after the name, or the second line of a card. change= washes the row green, amber, or red with a chip, for a diff. size="12 KB" is a pill at the right of the row. mark=on emphasises the entry. The folder icon is the default; tone= tints it. | `dir "src" note="source code" icon=lucide:folder {` | | `file "<name>" note="<text>" icon=<icon> tone=<tone> change=<added\|modified\|removed> size="<size>" mark=<on\|off>` | A file, which holds nothing. Its icon is chosen from the name: TypeScript, JavaScript, JSON, Markdown, CSS, HTML, YAML, TOML, lock files, images, shell, Python, Go, Rust, Java, SQL, Dockerfile, and .gitignore each get one, and any other name gets a plain file icon. icon= overrides it. A name with a dot or a slash needs its quotes, as every name does. | `file "main.ts" note="entry point" change=modified size="2 KB"` | | `node "<Name>" note="<text>" icon=<icon> tone=<tone> change=<added\|modified\|removed> size="<size>" mark=<on\|off> {` | A person, a team, a service, or a topic, for org and outline trees. It has no id, because a tree has no cross references. Write its reports or dependencies inside { }, or leave the braces off for a leaf. | `node "Platform" note="12 engineers" icon=lucide:users {` | | `omitted <count> "<noun>"` | A quiet entry that says how many more exist and are not drawn: omitted 14 "files" draws "and 14 more files". Use it to keep a large folder short. | `omitted 14 "files"` | | `desc "<text>"` | The plain-language description of the tree, for assistive technology; the same as desc= on the header. | `desc "The layout of the repository."` | ### Swimlane A process whose steps sit in lanes, one for each actor or system, with decisions and hand-offs between the lanes, for business and integration flows. The header line is `swimlane "Title"`. | Statement | What it does | Example | |---|---|---| | `swimlane "Title" direction=<auto\|right\|down> numbered=on start=<n> desc="<text>"` | The header. right draws the lanes as bands with time running left to right, down draws them as columns with time running down, and auto (the default) keeps the bands unless they would be wider than a Markdown column. numbered=on draws a numbered badge on each transition in the order written, and start= sets the first number. | `swimlane "Order fulfilment" direction=right numbered=on` | | `lane <id> "Name" tone=<tone> icon=<set:name> {` | Opens a lane: the actor or system that performs the steps written inside it, drawn as a band or a column with its icon and name in a rail at its start. Lanes stack in the order written. Close it with } on its own line. Every step belongs to a lane. | `lane customer "Customer" icon=lucide:user {` | | `step <id> "Label" sub="<detail>" tone=<tone> icon=<set:name>` | A step, drawn as a card in its lane. sub= is the line under the name, and a \n starts a new line. A tone other than neutral tints the card, and green, amber, or red also draw every transition that arrives at it in a softer line of that tone. | `step charge "Charge card" sub="Payments service" icon=lucide:credit-card` | | `decision <id> "Question?" tone=<tone> icon=<set:name>` | A decision, drawn as a tinted pill with a branch icon: one way in, and a transition out for each answer, each labelled with the condition that takes it. The default tone is amber. | `decision stock "In stock?"` | | `start <id> "Label" tone=<tone> icon=<set:name>` | Where the process begins, drawn as a small pill. | `start received "Order received"` | | `end <id> "Label" tone=<tone> icon=<set:name>` | Where the process finishes, drawn as a small pill. A process may have several ends, such as delivered and cancelled, and a green, amber, or red one colors the line that reaches it. | `end delivered "Delivered" tone=green icon=status:success` | | `from -> to : "event [guard] / action" step=<n>` | A transition, which may cross lanes freely and may return to an earlier step. The label and each part of it are optional: the event, a guard in square brackets that must hold, and an action after a slash. --> draws a dashed line, for a reply or an asynchronous hand-off, and ..> a dotted one, for an event. step= sets or skips the badge of a numbered diagram. | `stock -> charge : "[in stock]"` | | `desc "<text>"` | A plain-language description of the process for the text description, as desc= on the header does. | `desc "How an order moves from the shop to the customer."` | ## Tones and settings | Setting | Values | |---|---| | Tones | `neutral`, `blue`, `violet`, `aqua`, `orange`, `green`, `amber`, `red` | | Flow directions (`direction=`) | `auto`, `right`, `down`, `left`, `up` | | Themes | `light`, `dark` | | Sequence frames | `alt`, `opt`, `loop`, `par` | | Schema column flags | `pk`, `fk`, `unique`, `nullable` | ## See also | Doc | Why | |---|---| | [Language reference](https://docs.plotwork.dev/reference/language.md) | Every page of the reference and what each covers | | [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md) | The language by example, kind by kind | | [Flow node types](https://docs.plotwork.dev/reference/language/flow-nodes.md) | The types a flow node can take | | [Diagnostic codes](https://docs.plotwork.dev/reference/language/diagnostics.md) | What every error and warning means | --- # Flow node types > Page: https://docs.plotwork.dev/reference/language/flow-nodes. Index of every page: https://docs.plotwork.dev/llms.txt Every type a flow node can take, with its shape, default tone and icon, the technology marks a node can carry, and the mark of a planned change. ## TL;DR | Question | Answer | |---|---| | How is a node written? | `node <id> <type> "Label"`, with optional `sub=`, `tone=`, `icon=`, `stack=`, and `change=` options. | | How many types are there? | 16: `person`, `agent`, `client`, `service`, `gateway`, `function`, `database`, `cache`, `queue`, `storage`, `identity`, `secret`, `external`, `step`, `decision`, `terminal`. | | Which types can be a sequence actor? | `person`, `agent`, `client`, `service`, `gateway`, `function`, `database`, `cache`, `queue`, `storage`, `identity`, `secret`, `external`, `step`. | | How do I show a technology? | `stack=` carries up to 3 marks, such as `stack=simple:angular,simple:typescript`. | | How do I show what a change does? | `change=` takes `create`, `update`, `replace`, `destroy`: a badge on the card and an outline in the change's tone. | ## Node types A flow node is written `node <id> <type> "Label"`, with optional `sub=`, `tone=`, `icon=`, `stack=`, and `change=` options. A type sets the default tone, icon, and look. A `step` has no icon, so it has no chip for its tone to colour, and a tone written on it tints the card the way a decision is tinted. A sequence actor may use any type marked yes in the last column. | Type | Shape | Default tone | Default icon | Outline | Fill | Actor | |---|---|---|---|---|---|---| | `person` | card | neutral | `lucide:user` | solid | plain | yes | | `agent` | card | violet | `lucide:bot` | solid | plain | yes | | `client` | card | neutral | `lucide:monitor-smartphone` | solid | plain | yes | | `service` | card | blue | `lucide:server` | solid | plain | yes | | `gateway` | card | blue | `lucide:network` | solid | plain | yes | | `function` | card | blue | `lucide:zap` | solid | plain | yes | | `database` | card | aqua | `lucide:database` | solid | plain | yes | | `cache` | card | orange | `lucide:database-zap` | solid | plain | yes | | `queue` | card | orange | `lucide:layers` | solid | plain | yes | | `storage` | card | aqua | `lucide:hard-drive` | solid | plain | yes | | `identity` | card | violet | `lucide:id-card` | solid | plain | yes | | `secret` | card | red | `lucide:lock-keyhole` | solid | plain | yes | | `external` | card | neutral | `lucide:cloud` | dashed | plain | yes | | `step` | card | neutral | `none` | solid | plain | yes | | `decision` | card | amber | `lucide:split` | solid | tinted | no | | `terminal` | pill | neutral | `none` | solid | plain | no | ## Technology marks `stack=` puts small technology marks on a card, under its text: `node app storage "Web app" stack=simple:angular,simple:typescript`. Each mark is an icon reference, usually from the `simple` set, checked like `icon=`. A card carries the number of marks in the limits table, each listed once and drawn in the order written. A mark is never drawn over the text or the icon, because the card grows to hold the row, and the text description lists the marks. A `terminal` pill has no room for marks and reports an error. ## Planned changes A review of infrastructure needs to see at a glance what a change makes, alters, and removes. `change=` marks a node with a round badge on its top edge and outlines the card in the change's tone: `node db database "orders-db" icon=aws:rds change=update`. The mark works on every card, an icon from a vendor set too, and on a pill. The badge sits in the card's top padding, so it never covers the text or the icon, and the text description names the change. The `terraform-plan` import sets it on every resource the plan changes. | Value | Sign | Tone | Means the thing is | |---|---|---|---| | `create` | `+` | green | made by the change | | `update` | `~` | amber | changed in place | | `replace` | `±` | orange | destroyed and made again | | `destroy` | `−` | red | destroyed by the change | ## See also | Doc | Why | |---|---| | [Language reference](https://docs.plotwork.dev/reference/language.md) | Every page of the reference and what each covers | | [Groups and overlays](https://docs.plotwork.dev/reference/language/groups.md) | Frames around nodes | | [Icon sets](https://docs.plotwork.dev/reference/language/icon-sets.md) | Where an `icon=` value comes from | | [Icon reference](https://docs.plotwork.dev/reference/icons.md) | Every bundled icon and how to find one | --- # Groups and overlays > Page: https://docs.plotwork.dev/reference/language/groups. Index of every page: https://docs.plotwork.dev/llms.txt How a group frames nodes and nested groups, the options it takes, and how an overlay draws a frame around members that layout placed apart. ## TL;DR | Question | Answer | |---|---| | How is a group written? | `group <id> "Name" type=<type> provider=<provider> sub="10.0.0.0/16" {`, its nodes, then `}` on its own line. | | Which options does a group take? | `type`, `provider`, `icon`, `tone`, `sub`, `stack`. | | How do I set the provider for the whole diagram? | Write `provider=` on the flow header; nested groups inherit it. | | How is an overlay written? | `overlay <id> "Name" members=<id>,<id>`, one line, after its members. | ## Flow groups A group draws a frame around nodes and nested groups. It is written `group <id> "Name" type=<type> provider=<provider> sub="10.0.0.0/16" {`, then its nodes, then `}` on its own line. `type=` says what the group is, from the closed list on the [group types page](https://docs.plotwork.dev/reference/language/group-types.md); the type gives the group an icon, a tone, and a frame style, and a group nested inside another is shaded one step stronger than the one around it, in light and in dark. `icon=` and `tone=` are optional overrides. The frame carries a corner tab in its top-left corner, which holds the group's icon and its name on one line. The tab grows to fit the name with the same padding on both sides, and a name longer than the widest node card is cut with an ellipsis and warning `W203`. `sub=` adds a short second line under the name, such as a network address, and `stack=` puts up to three small marks under that, such as a lock for a network rule or a technology logo. The tab grows to hold both and never covers its own icon. A group with no `type=` whose name gives its type for certain takes it: a name that is or ends with `VPC`, `Network`, `Subnet`, `Kafka`, `Queue`, `Event bus`, `Kubernetes`, `Region`, `AWS account`, `Data`, `Security`, `IAM`, or `Monitoring`, a name that is exactly `Production`, `Staging`, `Dev`, `Pipeline` or `Cluster`, or a region code such as `us-east-1` or `westeurope`. "Data science team" and "Data pipeline" stay plain. `type=none` keeps a group plain, and `icons=off` on the header turns this off. A group with no type and no icon shows only its name. `icon=none` removes any icon. An edge that passes under a tab goes behind it. | Option | Values | Effect | |---|---|---| | `type` | `cloud`, `account`, `region`, `zone`, `network`, `subnet`, `public-subnet`, `private-subnet`, `management-subnet`, `isolated-subnet`, `resource-group`, `cluster`, `scale-group`, `service-plan`, `web-tier`, `app-tier`, `data-tier`, `storage-account`, `messaging`, `edge`, `security`, `identity-boundary`, `trust-boundary`, `observability`, `ci-cd`, `environment`, `cloud-boundary`, `on-premises`, `client-side`, `external-systems` | What the group is. It sets the default icon, tone, and frame style. An unknown value is error `E124` with the nearest type. | | `provider` | `aws`, `gcp`, `azure`, `alibaba`, `oci`, `ibm`, `cloudflare`, `digitalocean`, `vercel` | Whose resources the group holds, so its type draws that provider's icon: `type=network provider=aws` draws `aws:vpc`. Nested groups inherit it. Write `provider=` on the flow header to set it for the whole diagram. | | `icon` | `set:name` or `none` | The icon in the tab, for example `aws:cloud`. It overrides the icon of the type. | | `tone` | `neutral`, `blue`, `violet`, `aqua`, `orange`, `green`, `amber`, `red` | Tints the frame, the tab, and the border. It overrides the tone of the type. A group with no type is `neutral`. | | `sub` | quoted text | A short second line in the tab under the name, such as an address range or a plan name. A line longer than the widest card is cut with an ellipsis and warning `W203`. | | `stack` | up to 3 icon references, separated by commas | Small marks in the tab, written like the marks of a node: `stack=lucide:lock,simple:terraform`. | **Diagram source** ```diagram flow "Typed groups" provider=aws direction=down group account "Production" type=account { group region "eu-west-1" type=region { group vpc "Application VPC" type=network { node fn function "Handler" icon=aws:lambda } } } ``` *Picture: Typed groups: 1 node, 0 connections.* **Text description** **Nodes:** - Account: Production - Region: eu-west-1 - Virtual network: Application VPC - Handler, function **Connections:** ## Overlays An overlay draws a dashed frame around nodes or groups it does not own, wherever layout put them: a scaling group that spans two zones, or a redundant set of machines inside a tier. It is one line, `overlay <id> "Name" members=<id>,<id>`, written after the members, and it takes the same look options as a group: `type`, `provider`, `icon`, `tone`, `sub`, `stack`. A dashed frame around nodes or groups it does not own, wherever layout puts them: a scaling group across two zones, or a redundant set inside a tier. Write it after the members, one line: overlay id "Name" members=a,b type=scale-group. Use a group when the frame should own what is inside it. - When every member sits in one group, the overlay is laid out as a frame of its own, so layout leaves room for its edge and tab and nothing can fall inside by accident. - When members sit in different groups, nothing moves: the frame is drawn around them after layout, above the group frames it crosses and below the edges and nodes. Its tab never touches a group tab, another overlay tab, a node, or an edge label: it slides along the top edge to a free stretch, and when there is none the frame grows by the same amount on every side until the tab clears them. - A frame that would also surround a node that is not a member is warning `W206`, with the three fixes: add the node to `members=`, put the members in one group one after another, or split the overlay in two. - A member must exist (`E137`), be listed once, not be an overlay, not sit inside another member (`E138`), and belong to no other overlay (`E139`). - Edges connect nodes, never an overlay or a group. **Diagram source** ```diagram flow "Redundant set" direction=down group tier "Web tier" type=web-tier sub="10.0.1.0/24" { node lb gateway "Load balancer" node w1 service "Web 1" node w2 service "Web 2" node w3 service "Web 3" } overlay set "Web machines" type=scale-group members=w1,w2,w3 lb -> w1 lb -> w2 lb -> w3 ``` *Picture: Redundant set: 4 nodes, 3 connections.* **Text description** **Nodes:** - Web tier: Web tier (10.0.1.0/24) - Load balancer, gateway - Web 1, service - Web 2, service - Web 3, service **Overlays:** - Scale group overlay: Web machines, around Web 1, Web 2, Web 3 **Connections:** - Load balancer to Web 1 - Load balancer to Web 2 - Load balancer to Web 3 ## See also | Doc | Why | |---|---| | [Language reference](https://docs.plotwork.dev/reference/language.md) | Every page of the reference and what each covers | | [Group types](https://docs.plotwork.dev/reference/language/group-types.md) | The closed list `type=` takes, with the frame and icon of each | | [Cloud architecture](https://docs.plotwork.dev/cloud-architecture.md) | Typed groups, provider icons, and how to map a product name to an icon | | [Marks, second lines, and overlays](https://docs.plotwork.dev/cloud-architecture/marks-and-overlays.md) | The same features drawn and explained by example | --- # Group types > Page: https://docs.plotwork.dev/reference/language/group-types. Index of every page: https://docs.plotwork.dev/llms.txt Every group type with its frame style and tone, the icon each draws for each provider, and a complete cloud architecture diagram. ## TL;DR | Question | Answer | |---|---| | How many group types are there? | 30: `cloud`, `account`, `region`, `zone`, `network`, `subnet`, `public-subnet`, `private-subnet`, `management-subnet`, `isolated-subnet`, `resource-group`, `cluster`, `scale-group`, `service-plan`, `web-tier`, `app-tier`, `data-tier`, `storage-account`, `messaging`, `edge`, `security`, `identity-boundary`, `trust-boundary`, `observability`, `ci-cd`, `environment`, `cloud-boundary`, `on-premises`, `client-side`, `external-systems`. | | What does a type decide? | The icon in the corner tab, the tone, and the frame style: dashed for boundaries, solid for containers, dotted for things outside the system. | | Which providers change the icon? | `aws`, `gcp`, `azure`, `alibaba`, `oci`, `ibm`, `cloudflare`, `digitalocean`, `vercel`. With none, a type draws its generic icon. | | What is a group with no type? | A frame with only its name, in the `neutral` tone, unless its name gives its type for certain, such as `VPC`, `Production` or `Kafka`. `type=none` keeps it plain. | ## Types Pick the type that says what the group is, not what it looks like. The frame style carries meaning: network and trust boundaries are dashed, logical containers are solid, and things outside the system are dotted. Nesting deepens the shade, so an account, a region, and a network read as three levels. The [group types guide](https://docs.plotwork.dev/cloud-architecture/group-types.md) draws every type. | `type=` | Use it for | Frame | Tone | |---|---|---|---| | `cloud` | A provider's whole cloud: the outermost frame that holds its accounts, regions, and networks. | solid | neutral | | `account` | An account, subscription, or project: the container of one provider's resources that bills and grants access together. | solid | neutral | | `region` | A geographic region of a provider, such as eu-west-1 or West Europe. | dashed | neutral | | `zone` | An availability zone inside a region, drawn as a column. | dashed | neutral | | `network` | A VPC, VNet, or other private network that holds subnets and resources. | dashed | blue | | `subnet` | A subnet inside a network whose role is not public, private, management, or isolated. | solid | neutral | | `public-subnet` | A subnet whose resources can be reached from the internet, such as load balancers and bastion hosts. | solid | green | | `private-subnet` | A subnet with no direct route from the internet, such as application servers and databases. | solid | violet | | `management-subnet` | A subnet for administration: jump hosts, bastions, and tooling that operators use. | solid | amber | | `isolated-subnet` | A subnet with no route in or out of the network, such as a database that only the application tier can reach. | solid | red | | `resource-group` | A logical group of resources that are managed together, such as an Azure resource group. | solid | neutral | | `cluster` | A Kubernetes or container cluster. | solid | violet | | `scale-group` | An auto scaling group, scale set, or managed instance group of identical machines. | solid | aqua | | `service-plan` | A hosting plan that runs several apps on shared compute, such as an App Service plan. | solid | violet | | `web-tier` | The layer that faces users: web servers and the load balancer in front of them. | solid | blue | | `app-tier` | The layer that runs application code: application servers, services, or functions. | solid | violet | | `data-tier` | The layer that holds databases and caches. | solid | aqua | | `storage-account` | A storage account or a set of buckets and file shares. | solid | amber | | `messaging` | Queues, topics, and event buses that decouple services. | solid | orange | | `edge` | The public edge in front of the application: DNS, CDN, and front doors. | solid | blue | | `security` | Security services such as firewalls, web application firewalls, and key management. | solid | red | | `identity-boundary` | The area that authenticates users and issues tokens: an identity provider and its directory. | dashed | violet | | `trust-boundary` | A line across which trust changes: what is inside is checked differently from what is outside. | dashed | neutral | | `observability` | Monitoring, logging, and tracing. | solid | aqua | | `ci-cd` | A build and release pipeline with its repositories. | solid | violet | | `environment` | A deployment environment such as development, staging, or production. | solid | amber | | `cloud-boundary` | The line between your own premises and the cloud: everything inside is on the cloud side. | dotted | blue | | `on-premises` | A data center or office network that you run yourself, outside any cloud. | dotted | neutral | | `client-side` | Browsers, mobile apps, and devices that call the system. | dotted | neutral | | `external-systems` | Third-party services and partners that you call but do not run. | dotted | neutral | ## Icons by provider Without a provider a type draws its generic icon. With one, the type draws that provider's icon where the provider has one, and a provider with no icon of its own for a type draws the generic one. Each cell shows the icon that is drawn and its name. | `type=` | No provider | AWS | Azure | Google Cloud | |---|---|---|---|---| | `cloud` | `lucide:cloud` | `aws:cloud-group` | `azure:cloud` | `gcp:cloud` | | `account` | `lucide:cloud` | `aws:account` | `azure:subscriptions` | `gcp:cloud` | | `region` | `lucide:map-pin` | `aws:region` | `lucide:map-pin` | `lucide:map-pin` | | `zone` | `lucide:layers` | `lucide:layers` | `lucide:layers` | `lucide:layers` | | `network` | `lucide:network` | `aws:virtual-private-cloud-vpc` | `azure:virtual-network` | `gcp:vpc` | | `subnet` | `lucide:waypoints` | `lucide:waypoints` | `azure:subnet` | `lucide:waypoints` | | `public-subnet` | `lucide:lock-open` | `aws:public-subnet` | `lucide:lock-open` | `lucide:lock-open` | | `private-subnet` | `lucide:lock` | `aws:private-subnet` | `lucide:lock` | `lucide:lock` | | `management-subnet` | `lucide:settings` | `lucide:settings` | `lucide:settings` | `lucide:settings` | | `isolated-subnet` | `lucide:door-closed` | `lucide:door-closed` | `lucide:door-closed` | `lucide:door-closed` | | `resource-group` | `lucide:boxes` | `lucide:boxes` | `azure:resource-groups` | `lucide:boxes` | | `cluster` | `lucide:container` | `aws:eks` | `azure:aks` | `gcp:gke` | | `scale-group` | `lucide:server-cog` | `aws:auto-scaling-group` | `azure:vm-scale-sets` | `gcp:compute-engine` | | `service-plan` | `lucide:server` | `aws:ecs` | `azure:app-service-plans` | `gcp:cloud-run` | | `web-tier` | `lucide:monitor` | `lucide:monitor` | `lucide:monitor` | `lucide:monitor` | | `app-tier` | `lucide:server` | `lucide:server` | `lucide:server` | `lucide:server` | | `data-tier` | `lucide:database` | `aws:rds` | `azure:sql-database` | `gcp:cloud-sql` | | `storage-account` | `lucide:hard-drive` | `aws:s3` | `azure:blob-storage` | `gcp:cloud-storage` | | `messaging` | `lucide:send` | `aws:sqs` | `azure:service-bus` | `gcp:pubsub` | | `edge` | `lucide:globe` | `aws:cloudfront` | `azure:front-door` | `gcp:cloud-cdn` | | `security` | `lucide:shield-check` | `aws:waf` | `azure:firewall` | `gcp:security-identity` | | `identity-boundary` | `lucide:id-card` | `aws:iam` | `azure:entra-id` | `gcp:iam` | | `trust-boundary` | `lucide:shield` | `lucide:shield` | `lucide:shield` | `lucide:shield` | | `observability` | `lucide:activity` | `aws:cloudwatch` | `azure:monitor` | `gcp:observability` | | `ci-cd` | `lucide:workflow` | `aws:codepipeline` | `azure:devops` | `gcp:devops` | | `environment` | `lucide:rocket` | `lucide:rocket` | `lucide:rocket` | `lucide:rocket` | | `cloud-boundary` | `lucide:cable` | `lucide:cable` | `lucide:cable` | `lucide:cable` | | `on-premises` | `lucide:factory` | `aws:corporate-data-center` | `lucide:factory` | `lucide:factory` | | `client-side` | `lucide:monitor-smartphone` | `lucide:monitor-smartphone` | `lucide:monitor-smartphone` | `lucide:monitor-smartphone` | | `external-systems` | `lucide:plug` | `lucide:plug` | `lucide:plug` | `lucide:plug` | ## A complete example ### Flow: Web application on a cloud A complete architecture diagram. Every group has a type, every service has a provider icon, two nodes carry stack marks, and two edges are dotted. **Diagram source** ```diagram flow "Web application on a cloud" provider=azure direction=down desc="Visitors load a single-page app from storage through a content delivery network and sign in through an identity service. The app calls an API on an application plan, which stores uploads, reads a database, and hands slow work to a queue that a background worker drains. A pipeline builds and deploys the API, and a monitoring service collects telemetry." node visitors client "Visitors" sub="Browsers" group edge "Edge" type=edge { node cdn gateway "Content delivery" icon=azure:cdn } group sign-in "Sign-in" type=identity-boundary { node idp identity "Identity service" icon=azure:entra-id } group files "Storage" type=storage-account { node site storage "Web app files" icon=azure:blob-storage stack=simple:react,simple:typescript node uploads storage "Uploads" icon=azure:blob-storage } group plan "Application plan" type=service-plan { node api service "Orders API" icon=azure:app-service stack=simple:nodedotjs } group messaging "Messaging" type=messaging { node jobs queue "Job queue" icon=azure:service-bus } node worker function "Background worker" icon=azure:functions stack=simple:nodedotjs node db database "Database" icon=azure:sql-database group delivery "Delivery pipeline" type=ci-cd { node repo step "Source code" icon=lucide:git-branch node build step "Build pipeline" icon=azure:devops } group monitoring "Monitoring" type=observability { node telemetry service "Metrics and logs" icon=azure:monitor } visitors -> cdn : "HTTPS" visitors -> idp : "sign in" role=identity cdn -> site cdn -> api : "API calls" api -> uploads : "store files" role=data api -> jobs : "enqueue" role=async jobs ..> worker : "event" worker -> db : "writes" role=data api -> db : "reads" role=data repo -> build build ..> api : "deploys image" api ..> telemetry : "telemetry" ``` *Picture: Web application on a cloud: 12 nodes, 12 connections. Visitors load a single-page app from storage through a content delivery network and sign in through an identity service. The app calls an API on an application plan, which stores uploads, reads a database, and hands slow work to a queue that a background worker drains. A pipeline builds and deploys the API, and a monitoring service collects telemetry.* **Text description** **Nodes:** - Visitors (Browsers), client - Background worker, function, stack: nodedotjs - Database, database - Edge: Edge - Content delivery, gateway - Identity: Sign-in - Identity service, identity - Storage: Storage - Web app files, storage, stack: react, typescript - Uploads, storage - Service plan: Application plan - Orders API, service, stack: nodedotjs - Messaging: Messaging - Job queue, queue - CI and CD: Delivery pipeline - Source code, step - Build pipeline, step - Observability: Monitoring - Metrics and logs, service **Connections:** - Visitors to Content delivery: HTTPS - Visitors to Identity service (identity line): sign in - Content delivery to Web app files - Content delivery to Orders API: API calls - Orders API to Uploads (stored data line): store files - Orders API to Job queue (asynchronous line): enqueue - Job queue to Background worker (dotted): event - Background worker to Database (stored data line): writes - Orders API to Database (stored data line): reads - Source code to Build pipeline - Build pipeline to Orders API (dotted): deploys image - Orders API to Metrics and logs (dotted): telemetry ## See also | Doc | Why | |---|---| | [Language reference](https://docs.plotwork.dev/reference/language.md) | Every page of the reference and what each covers | | [Groups and overlays](https://docs.plotwork.dev/reference/language/groups.md) | The options a group takes | | [Group types guide](https://docs.plotwork.dev/cloud-architecture/group-types.md) | Every type drawn on its own | | [Icon names and lookup](https://docs.plotwork.dev/cloud-architecture/icon-names.md) | How to find the icon for a service | --- # Arrows and edge labels > Page: https://docs.plotwork.dev/reference/language/edges. Index of every page: https://docs.plotwork.dev/llms.txt The arrow tokens, the labels and icons an edge can carry, the level edge that keeps mirrored columns side by side, and the options that animate a line, number the steps, or color a line by what it carries. ## TL;DR | Question | Answer | |---|---| | Which arrows exist? | `<-->`, `-->`, `<->`, `..>`, `->`, `--`. | | How is an edge labelled? | `a -> b : "label"`, and with an icon `a -> b : "label" icon=status:yes`. | | Which labels get an icon without being asked? | `yes`, `no`, `true`, `false`, and a flow label that names a relationship with a verb, such as `reads`, `writes orders` or `deploys to`. Write `icon=none` on the edge to remove it, or `icons=off` on the header to remove them all. | | Can an edge join a group? | No. Edges connect nodes, never an overlay or a group. | | How do I make data move along a line? | `a -> b animate=on`, or `animate=on` on the header for every line with an arrowhead. A viewer who asks for reduced motion sees still lines. | | How do I number the steps? | `numbered=on` on the header. `start=5` begins at 5, and `step=off` or `step=12` on a line skips it or sets its number. | | How do I show what a line carries? | `role=data`, `identity`, `async` or `private` on a flow edge colors the line by meaning, and a legend under the diagram names each role used. `legend=off` on the header hides it. | ## Arrows | Token | Line | Arrowheads | Use it for | |---|---|---|---| | `<-->` | dashed | both | A two-way link that is optional or indirect. | | `-->` | dashed | end | A reply, a read, or a link that is optional or indirect. | | `<->` | solid | both | A two-way call or sync. | | `..>` | dotted | end | An asynchronous call: a webhook, an event, a pull, or a push that is not waited for. | | `->` | solid | end | A call or a flow of data. | | `--` | solid | none | An association with no direction. | ## Level edges An edge between two nodes that are already side by side is drawn straight across instead of as a step forward. Two nodes count as side by side when other edges reach both and, without this edge, they sit the same distance from the start, as a primary and a replica database do. Such an edge, often dotted and labelled "replication", never pushes one of the two a layer away from its twin, so mirrored columns such as two availability zones stay level. When something sits between them the edge goes round it. ## Labels and icons An edge may carry a label and an icon: `a -> b : "label" icon=status:yes`. These labels get an icon without being asked, so the branches of a decision read at a glance. Write `icon=none` on the edge to remove it. | Label | Icon | |---|---| | `yes` | `status:yes` | | `no` | `status:no` | | `true` | `status:yes` | | `false` | `status:no` | ### Verbs A flow edge label that names a relationship with a verb draws that relationship's icon, the same one an imported diagram draws for it. The whole label may be the verb (`reads`, `depends on`), or start with it and go on with what is acted on, in five words or fewer (`writes orders`, `publishes OrderPlaced to Kafka`). The longest phrase wins, so `sends logs` is reporting and not sending, and a label that starts with a thing named after a verb, such as `Read replica` or `Pull request`, draws nothing. `icon=` on the edge picks another icon, `icon=none` keeps the label plain, and `icons=off` on the header turns every automatic icon off. | Icon | Phrases | |---|---| | `lucide:eye` | `reads`, `read`, `reading`, `reads from`, `read from`, `queries`, `query`, `querying`, `looks up`, `look up`, `selects`, `select from`, `loads from`, `fetches from`, `read by`, `queried by`, `looked up by`, `loaded from`, `fetched from` | | `lucide:square-pen` | `writes`, `write`, `writing`, `writes to`, `write to`, `inserts`, `insert`, `inserts into`, `updates`, `update`, `saves`, `save`, `saves to`, `persists`, `persist`, `persists to`, `deletes`, `delete`, `deletes from`, `uploads`, `upload`, `uploads to`, `upload to`, `written to`, `written by`, `written into`, `inserted into`, `updated by`, `saved to`, `saved in`, `persisted to`, `persisted in`, `deleted from`, `uploaded to`, `uploaded by` | | `lucide:database` | `stores`, `store`, `stores in`, `store in`, `caches`, `caches in`, `reads and writes`, `reads writes`, `read write`, `read and write`, `stored in`, `stored by`, `cached in`, `cached by` | | `lucide:hard-drive` | `mounts`, `mount`, `mounted by`, `mounted at`, `mounted in`, `mounted on` | | `lucide:send` | `sends`, `send`, `sends to`, `publishes`, `publish`, `publishes to`, `publish to`, `emits`, `emit`, `produces`, `produce`, `produces to`, `enqueues`, `enqueue`, `pushes to`, `notifies`, `notify`, `fires`, `sent to`, `sent by`, `published to`, `published by`, `emitted to`, `emitted by`, `produced to`, `enqueued to`, `enqueued in`, `enqueued on`, `pushed to`, `notified by` | | `lucide:inbox` | `receives`, `receive`, `consumes`, `consume`, `consumes from`, `subscribes`, `subscribes to`, `subscribe to`, `listens to`, `listens on`, `polls`, `dequeues`, `received by`, `received from`, `consumed by`, `consumed from`, `subscribed to`, `polled by`, `polled from`, `dequeued by` | | `lucide:arrow-right-left` | `calls`, `call`, `invokes`, `invoke`, `requests`, `request`, `fetches`, `fetch`, `queries api`, `http`, `https`, `grpc`, `rest`, `graphql`, `called by`, `invoked by`, `requested by`, `requested from`, `fetched by` | | `lucide:globe` | `routes`, `routes to`, `route to`, `forwards`, `forwards to`, `forward to`, `proxies`, `proxies to`, `redirects to`, `load balances`, `routed to`, `routed by`, `routed through`, `forwarded to`, `forwarded by`, `proxied to`, `proxied by`, `proxied through`, `redirected to`, `load balanced by` | | `lucide:link` | `depends on`, `depend on`, `requires`, `needs`, `required by`, `needed by`, `depended on by` | | `lucide:plug` | `provides`, `exposes`, `serves`, `provided by`, `exposed by`, `served by`, `served from` | | `lucide:zap` | `triggers`, `trigger`, `starts`, `kicks off`, `schedules`, `wakes`, `triggered by`, `started by`, `kicked off by`, `scheduled by`, `woken by` | | `lucide:rocket` | `deploys`, `deploy`, `deploys to`, `deploy to`, `releases to`, `ships to`, `promotes to`, `rolls out`, `rolls out to`, `deployed to`, `deployed by`, `deployed on`, `released to`, `shipped to`, `promoted to`, `rolled out to`, `rolled out by` | | `lucide:key-round` | `authenticates`, `authenticate`, `authenticates with`, `authenticates against`, `authorizes`, `authorize`, `authorises`, `authorise`, `signs in`, `sign in`, `signs in with`, `logs in`, `log in`, `logs in with`, `grants`, `grant`, `assumes role`, `issues token`, `issues tokens`, `verifies token`, `verify token`, `validates token`, `validate token`, `logs out`, `log out`, `signs out`, `sign out`, `authenticated by`, `authenticated with`, `authenticated against`, `authorized by`, `authorised by`, `granted by`, `granted to`, `assumed by`, `issued by` | | `lucide:key-round` | `reads secret`, `reads secrets`, `read secret`, `read secrets`, `read credentials`, `fetches secret`, `fetches secrets`, `reads credentials`, `gets secret`, `gets secrets` | | `lucide:activity` | `reports`, `reports to`, `logs`, `logs to`, `metrics`, `telemetry`, `traces`, `logs and metrics`, `sends logs`, `sends logs to`, `sends metrics`, `sends metrics to`, `sends telemetry`, `sends traces`, `ships logs`, `ships logs to`, `exports metrics`, `exports traces`, `emits metrics`, `emits logs`, `reported to`, `reported by`, `logged to` | | `lucide:download` | `pulls image`, `pulls images`, `pulls images from`, `pulls image from`, `pulled from` | ## Animated lines, step numbers and line roles Two opt-in options draw more than the shape of a diagram. `animate=on` makes dashes travel along a line toward its arrowhead, so a reader sees data or events move. `numbered=on` draws a numbered badge on each edge or message in the order written, so a reader can follow a story by number. Both are off unless written, and a diagram that uses neither is drawn exactly as before. They work on flow and sequence diagrams, and a schema takes neither. | Option | Where | Takes | What it does | |---|---|---|---| | `animate` | The header line | on, off | Dashes travel along every line that has an arrowhead, in the direction of the arrowhead, so a reader sees data or events move. A line opts out with animate=off. The default is off, and a viewer who asks for reduced motion sees still lines. Example: `flow "Data flow" animate=on` | | `animate` | An edge or a message | on, off | Animates this one line, or with off keeps it still when the header turns animation on. A line with no arrowhead (--) has no direction, so animate=on on it is an error. A line with two arrowheads animates from its first end to its second. Example: `api -> db : "write" animate=on` | | `numbered` | The header line | on, off | Draws a numbered badge on each edge or message in the order written. One counter runs through loops, alternatives, and parallel blocks. The text description carries the numbers. Example: `sequence "Login" numbered=on` | | `start` | The header line | 0 to 999 | The number of the first badge. The default is 1. It needs numbered=on. Example: `sequence "Login" numbered=on start=5` | | `step` | An edge or a message | off, or 0 to 999 | step=off leaves this line without a badge and does not use up a number. step=12 gives it the badge 12, and the next line continues from 13. It needs numbered=on in the header. Example: `api -> cache : "warm" step=off` | | `role` | An edge or a message | data, identity, async, private | Flow edges only. What the line carries: data (stored data), identity (sign-in, tokens), async (events, queues), private (private network). It colours the line, a solid async line is dotted, and a legend names each role used. Example: `api -> db : "read" role=data` | | `legend` | The header line | on, off | Flow only. off hides the legend of line roles. Example: `flow "Platform" legend=off` | ### Animated lines - The dashes move in the direction of the arrowhead. A line with two arrowheads (`<->`) moves from its first end to its second, and a line with none (`--`) never moves, because nothing flows along it; `animate=on` written on one is an error. - A solid line is drawn as a dash and a gap only while it moves. With motion off it is the plain solid line, so the difference between a call (`->`), a reply (`-->`), and an asynchronous call (`..>`) stays in the picture. - The arrowheads, the labels, and the numbered badges never move. - A viewer whose system asks for reduced motion sees still lines, and a picture shown where scripts and style rules do not run is still too. The motion is a CSS animation inside the SVG, so it runs in an image tag, on GitHub, and in the docs. - Animate the lines that carry the story. When every line moves, nothing stands out; write `animate=on` on the header, then `animate=off` on the lines that should rest. **Diagram source** ```diagram flow "Event pipeline" direction=right animate=on node app client "Web app" node api service "Ingest API" node queue queue "Event queue" node worker function "Worker" node db database "Warehouse" app -> api : "POST /events" api -> queue : "enqueue" role=async queue ..> worker : "pull" worker -> db : "write batch" role=data worker --> api : "ack" animate=off ``` *Picture: Event pipeline: 5 nodes, 5 connections.* **Text description** **Nodes:** - Web app, client - Ingest API, service - Event queue, queue - Worker, function - Warehouse, database **Connections:** - Web app to Ingest API: POST /events - Ingest API to Event queue (asynchronous line): enqueue - Event queue to Worker (dotted): pull - Worker to Warehouse (stored data line): write batch - Worker to Ingest API (dashed): ack ### Step numbers - The count runs in the order written, through loops, alternatives, and parallel blocks, and starts at 1. `start=5` begins at 5. - `step=off` on a line leaves it without a badge and does not use up a number. `step=12` sets the badge to 12, and the next line continues from 13. - A sequence message has its badge directly above the arrow, before its label. A flow edge has its badge at the left end of its label, and an edge with no label has the badge on its line. Layout makes room for the badge, so it never covers a node or a label. - The text description carries the numbers, as in `1. User to API: POST /login`, so a reader who cannot see the badges has them. - More than 25 numbered steps in one diagram is the density warning `W200`: split the diagram into phases that each restart at 1. **Diagram source** ```diagram sequence "Sign in" numbered=on actor user person "User" actor web client "Browser" actor api service "Auth API" user -> web : "Open the sign in page" web -> api : "POST /login" loop "up to 3 attempts" { api -> api : "Check the password" } api --> web : "200 with a session cookie" web --> user : "Show the account page" step=off ``` *Picture: Sign in: 3 participants.* **Text description** **Participants:** - User, person - Browser, client - Auth API, service **Steps:** - 1. User to Browser: Open the sign in page - 2. Browser to Auth API: POST /login - loop: up to 3 attempts - 3. Auth API to Auth API: Check the password - 4. Auth API to Browser (dashed): 200 with a session cookie - Browser to User (dashed): Show the account page ### Line roles A flow edge can say what it carries with `role=`: `data` for reads and writes of stored data, `identity` for sign-in and tokens, `async` for events and messages, and `private` for private network links. Each role has its own color, an `async` line drawn with a solid arrow is dotted, and the outline names the role in words, so the meaning never rests on color alone. A legend under the diagram lists the roles used. A brand theme colors the roles with the brand. Red, green and amber are never used, because they mean a status. A role is never given by itself, because a colour means something. When an arrow's label names a relationship that has one, such as `reads` (data), `publishes` (async), or `authenticates` (identity), and the arrow has no `role=` and no `icon=`, warning `W500` names the arrows, one warning per role, with the `role=` to add in its hint. `W500` is a style suggestion, like `W204` and `W205`: `validate --strict` and the Action's `fail-on: warning` pass it. ## Make it look finished - Label each arrow with a verb, such as "reads", "writes orders", "publishes OrderPlaced", "calls", "deploys to", or "authenticates": the label then draws the icon of that relationship (verbIcons). icon= picks another icon; icon=none keeps the label plain. - Add role= to the arrows whose line means something: role=data for reads and writes of stored data, role=async for messages nobody waits for, role=identity for sign-in and credentials, role=private for private network links. The line takes the role's colour and the legend names it; warning W500 lists the arrows to mark. - Give every group a type= (network, subnet, environment, data-tier, messaging, cluster, ci-cd, observability, and the rest), so its frame, tone, and icon follow. A group named plainly, such as "VPC", "Production", "Kafka", or "Monitoring", gets its type by itself; type=none keeps it plain. - Name each node after the product it is ("Postgres", "Redis", "Amazon S3") so it draws the mark, and choose the node type that says what it is. - Keep labels to two to four words; the picture carries the rest. ## See also | Doc | Why | |---|---| | [Language reference](https://docs.plotwork.dev/reference/language.md) | Every page of the reference and what each covers | | [Flow node types](https://docs.plotwork.dev/reference/language/flow-nodes.md) | The nodes an edge joins | | [Icon sets](https://docs.plotwork.dev/reference/language/icon-sets.md) | The status icons an edge label can use | | [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md) | Edges in context | --- # Icon sets and status icons > Page: https://docs.plotwork.dev/reference/language/icon-sets. Index of every page: https://docs.plotwork.dev/llms.txt The icon sets a diagram can draw from, how a provider product is written, and the status icons that keep their own color. ## TL;DR | Question | Answer | |---|---| | How many icons are bundled? | 2283, in 12 sets: `lucide`, `simple`, `status`, `aws`, `gcp`, `azure`, `alibaba`, `oci`, `ibm`, `cloudflare`, `digitalocean`, `vercel`. | | How is an icon written? | `set:name`, such as `lucide:database` or `aws:lambda`. | | How do I find a name? | Call `search_icons` or `map_icons`. Never guess a name. | | Which icons keep their own color? | The 20 status icons, such as `status:yes`, `status:no`, `status:success`. | | Does an item get an icon without icon=? | Yes, when its label names one for certain: `node db database "Postgres"` draws the Postgres mark. `icon=none` keeps one item plain, and `icons=off` on the header keeps them all plain. | ## Icon sets | Set | Contents | |---|---| | `lucide` | General icons such as `lucide:database` and `lucide:user`. | | `simple` | Technology and brand logos such as `simple:postgresql`. | | `status` | Colored status and outcome icons such as `status:success`; each keeps its own color on any node. | | `aws` | Amazon Web Services: the provider (`aws:cloud`) and 825 products, such as `aws:ec2`. | | `gcp` | Google Cloud: the provider (`gcp:cloud`) and 78 products, such as `gcp:compute-engine`. | | `azure` | Microsoft Azure: the provider (`azure:cloud`) and 668 products, such as `azure:virtual-machines`. | | `alibaba` | Alibaba Cloud: the provider (`alibaba:cloud`) and 17 products, such as `alibaba:ecs`. | | `oci` | Oracle Cloud Infrastructure: the provider (`oci:cloud`) and 8 products, such as `oci:compute`. | | `ibm` | IBM Cloud: the provider (`ibm:cloud`) and 12 products, such as `ibm:code-engine`. | | `cloudflare` | Cloudflare: the provider (`cloudflare:cloud`) and 41 products, such as `cloudflare:workers`. | | `digitalocean` | DigitalOcean: the provider (`digitalocean:cloud`) and 7 products, such as `digitalocean:droplets`. | | `vercel` | Vercel: the provider (`vercel:cloud`) and 3 products, such as `vercel:functions`. | ## Provider icons Cloud and platform products are written `<provider>:<product>`, for example `aws:lambda`, `gcp:bigquery`, `azure:functions`, and `alibaba:oss`. Each provider set also holds `<provider>:cloud`, the provider itself, for the group that holds its resources. A product with a licensed brand mark draws it. Every other product draws one glyph for its kind of service (a function, a queue, object storage), so the same kind of service looks the same across providers. Where whoever runs the renderer has installed the official AWS, Azure, or Google Cloud icon pack, the products it covers draw the vendor's own artwork instead, unmodified, on a light tile; the names stay the same. Search understands product names, abbreviations, and phrases: `lambda`, `s3`, `gke`, `cosmos db`, `serverless function`, and `azure object storage` all find the right icon. The [icon reference](https://docs.plotwork.dev/reference/icons.md) lists every product. ## Status icons Status icons keep their own color whatever tone their node has, so a green check stays green on a violet node. Use them on nodes, actors, tables, groups, and the terminal pill. | Icon | Color | Meaning | |---|---|---| | `status:yes` | green | An affirmative answer or a passed check; shown on a branch labelled yes. | | `status:no` | red | A negative answer or a failed check; shown on a branch labelled no. | | `status:success` | green | Succeeded, passed, or done. | | `status:error` | red | Failed or rejected. | | `status:warning` | amber | Needs attention but did not fail. | | `status:info` | blue | Informational note. | | `status:pending` | neutral | Waiting for something. | | `status:running` | blue | In progress. | | `status:retry` | amber | Will be tried again. | | `status:skipped` | neutral | Intentionally not run. | | `status:proceed` | green | Allowed to continue to the next step. | | `status:allowed` | green | Access or a request was granted. | | `status:denied` | red | Access or a request was refused. | | `status:blocked` | red | Stopped by a rule or policy. | | `status:bug` | red | A defect or known issue. | | `status:verified` | green | Checked and confirmed, such as a verified token or identity. | | `status:expired` | amber | No longer valid because time ran out. | | `status:revoked` | red | Taken back on purpose; a revoked grant or token. | | `status:up` | green | Increase or improvement. | | `status:down` | red | Decrease or regression. | ## Icons a label names auto (the default): an item with no icon= gets the icon its label, else its sub= or tech=, names for certain: a brand ("Postgres"), a cloud product ("Amazon S3"), or in some kinds a role ("Email"). A name that is also an everyday word ("Vault") stays plain. icon=none keeps one item plain; off keeps all plain, with flow edge labels, and stops a flow group taking its type from its name. Example: `flow "Checkout" icons=off` The header of these kinds takes `icons=` (auto, off): `flow`, `sequence`, `mindmap`, `treemap`, `pie`, `quadrant`, `layers`, `c4`, `matrix`, `sankey`, `xychart`, `swimlane`. The other kinds draw a mark of their own, or hold moments and conditions rather than things, so they never guess. The plain icons for common roles are drawn on a flow or sequence `service` or `external` item, a C4 element, a layers block, a mind map topic, and a swimlane lane, when the label is three words or fewer. The last role word wins, so `Payment queue` is a queue. | Icon | Words in the label | |---|---| | `lucide:chart-line` | `analytics` | | `lucide:key-round` | `auth`, `authentication` | | `lucide:credit-card` | `billing`, `payment`, `payments` | | `lucide:hard-drive` | `bucket`, `storage` | | `lucide:database-zap` | `cache` | | `lucide:globe` | `cdn` | | `lucide:message-square` | `chat` | | `lucide:shopping-cart` | `checkout` | | `lucide:user` | `customer`, `user` | | `lucide:users` | `customers`, `team`, `users` | | `lucide:mail` | `email`, `emails`, `mailer` | | `lucide:log-in` | `login` | | `lucide:scroll-text` | `logging`, `logs` | | `lucide:activity` | `monitoring` | | `lucide:bell` | `notification`, `notifications` | | `lucide:layers` | `queue` | | `lucide:calendar-clock` | `scheduler` | | `lucide:search` | `search` | | `lucide:webhook` | `webhook`, `webhooks` | ## See also | Doc | Why | |---|---| | [Language reference](https://docs.plotwork.dev/reference/language.md) | Every page of the reference and what each covers | | [Icon reference](https://docs.plotwork.dev/reference/icons.md) | Every bundled icon | | [Icon names and lookup](https://docs.plotwork.dev/cloud-architecture/icon-names.md) | How a product name maps to an icon | | [Trademarks and icons](https://docs.plotwork.dev/trademarks-and-icons.md) | What the vendor and brand marks may be used for | --- # Limits and quality checks > Page: https://docs.plotwork.dev/reference/language/limits. Index of every page: https://docs.plotwork.dev/llms.txt How large a diagram may be, the quality warnings that fire past the density limits, and the two suggestions that name an icon or a group type to add. ## TL;DR | Question | Answer | |---|---| | How big can a diagram be? | 50000 characters of source. Longer source is refused. | | When does a density warning start? | At 15 flow nodes (one more for each group, up to 25), 6 tables, 6 actors, 40 git graph commits, 7 git graph branches, 40 Gantt chart tasks and milestones, 12 class diagram classes, 12 class diagram members in one class, 15 state states, 3 state composite nesting, 12 timeline periods on a horizontal rail, 24 timeline periods on a vertical rail, 5 timeline events in one period, 48 timeline events, 16 user journey tasks, 8 user journey actors, 40 mind map topics, 8 mind map branches, 4 mind map levels, 9 mind map topics under one topic, 80 treemap treemap items, 6 share chart slices, 3 share chart points between slices, 40 quadrant chart points, 8 layered architecture blocks in a layer, 12 C4 diagram elements, 20 matrix rows, 12 matrix columns, 30 matrix items, 8 Sankey diagram sankey columns, 6 XY chart series, 60 XY chart places, 60 tree tree rows, 8 swimlane lanes, or 40 swimlane steps. | | Which warnings suggest a fix? | `W204` suggests an `icon=` and `W205` suggests a `type=`, each with the exact text to add. | | Do the suggestions change the picture? | No. An icon the label names for certain is already drawn (`icons=auto`); `W204` names the ones left for the author to confirm. | ## Limits | Limit | Value | Effect | |---|---|---| | Source length | 50000 | Longer source is refused. | | Flow nodes | 15, plus 1 for each group, up to 25 | More nodes produce a density warning. Groups break a diagram into parts, so each one allows a node more. | | Schema tables | 6 | More tables produce a density warning. | | Sequence actors | 6 | More actors produce a density warning. | | Git graph commits | 40 | More commits than this draws warning W200: a picture shows a release or a feature well, not a whole repository. More than 300 is refused with error E140. | | Git graph branches | 7 | More branches than this draws warning W200, because the lane colors then repeat. More than 24 is refused with error E140. | | Gantt chart tasks and milestones | 40 | One row each. More than this reads as a wall of bars: split the plan into charts by phase or team. A chart of more than 300 rows, 60 sections, or 3660 days is refused with E140. | | Class diagram classes | 12 | More classes than this in one diagram is a wall of boxes. Each package allows 2 more, up to 24. Past 200 classes or 400 relations a diagram is refused with E140, before any layout work. Split it along a package. | | Class diagram members in one class | 12 | A class box taller than this crowds the diagram around it. Show the members that matter to the design. A class of more than 60 members is refused with E140. | | State states | 15 | More states than this in one diagram, plus one for each composite state up to 25, draw a warning. Group related states into composite states, or split the diagram. Past 300 nodes, 600 transitions, or 6 levels of nesting, the diagram is refused. | | State composite nesting | 3 | Composite states nested deeper than this draw a warning. Draw the inner state machine as a diagram of its own. | | Timeline periods on a horizontal rail | 12 | More periods than this run past the width of a page and shrink the text. Set direction=down or split the timeline by era. More than 200 periods are refused. | | Timeline periods on a vertical rail | 24 | More periods than this make a picture too tall to take in. Split the timeline by era. | | Timeline events in one period | 5 | A stack taller than this outweighs its neighbours. Split the period in two, or keep the events that matter. More than 12 events in one period are refused. | | Timeline events | 48 | More events than this are a history, not a diagram. Split the timeline by era. More than 500 events are refused. | | User journey tasks | 16 | More tasks than this in one journey is more than a reader takes in at once (W200). Draw one journey for each stage. | | User journey actors | 8 | More actors than this share a tone, so some chips look alike (W351). Merge actors who always act together. | | Mind map topics | 40 | More topics than this draw a density warning, and more than 300 are refused. Split a big map by branch. | | Mind map branches | 8 | More branches round the root than this draw a warning. The branch tones repeat after seven. | | Mind map levels | 4 | More levels below the root than this draw a warning, and more than 10 are refused. | | Mind map topics under one topic | 9 | More topics branching from one topic than this draw a warning. Group them. | | Treemap treemap items | 80 | More than this reads as a field of unlabeled tiles: group the small ones into one "Other" item, or split the treemap by group. More than 300 items or 100 groups is refused with E140. Many tiles too small for a label also warn. | | Share chart slices | 6 | More slices than this in one ring draw a warning: the colours stop being easy to tell apart. Use fold= or a bar chart. More than 30 are refused with E140. | | Share chart points between slices | 3 | Slices whose shares are within this many percentage points of each other, and within a quarter of each other in size, draw a warning: a ring cannot show which is larger. Use a bar chart to rank them. | | Quadrant chart points | 40 | One mark each. More than this and a reader cannot weigh them: split the items by theme or team. A chart of more than 120 points is refused with E140. | | Layered architecture blocks in a layer | 8 | More than this wraps the layer to several rows and it stops reading as one tier: split the layer. More than 12 layers, 80 blocks, or 80 links is refused with E140. More than 20 links warns. | | C4 diagram elements | 12 | More elements than this in one diagram, plus 2 for each boundary up to 20, draw a warning. Split the diagram by level. Past 80 elements, 160 relationships, 40 boundaries, or 4 levels of nesting, the diagram is refused. | | Matrix rows | 20 | One strip each. More than this and a reader cannot weigh them: split by theme or team. More than 40 rows is refused with E140. | | Matrix columns | 12 | The most that can be drawn, refused with E140 past it. More than about 8 are wider than a Markdown column and shrink their text (W202). | | Matrix items | 30 | Risks on one grid. More than 60 is refused with E140. | | Sankey diagram sankey columns | 8 | More columns than this make a picture wider than a Markdown column. More than 12 columns, 60 nodes, or 150 flows is refused with E140. A node whose flows in and out differ, ribbons thinner than 2px, and more than 40 nodes or 100 flows also warn. | | XY chart series | 6 | More than this draws a warning: the colours stop being easy to tell apart. More than 8 is refused with E140. | | XY chart places | 60 | Places on the x axis; more is refused with E140. The labels of a crowded axis are thinned, with a warning. | | Tree tree rows | 60 | More rows than this in a file tree or an outline reads as a wall: draw one tree for each top-level folder, or fold the small ones into an omitted line. Past 6 levels also warns. More than 300 entries or 8 levels is refused with E140. | | Swimlane lanes | 8 | More lanes than this draw a warning. Past 12 lanes, 80 steps, or 160 transitions the diagram is refused. | | Swimlane steps | 40 | More steps than this draw a warning. Split a long process into one diagram for each phase. | | Stack marks | 3 | A node carries at most this many marks in `stack=`; one more is an error. | | Column width | 880px | A diagram wider than this shrinks to fit; if text would drop below readable size, a width warning appears. | ## Suggestions Two warnings are suggestions for architecture diagrams. They change nothing in the diagram: each gives the exact text to add, and an author who disagrees leaves it out or writes `icon=none`. The rules are fixed, so the same source always gets the same suggestions. | Code | Fires when | The suggestion | |---|---|---| | `W204` | A node has no `icon=`, and its label (or, when the label names nothing, its sub text) names exactly one product that the node does not draw. With the default `icons=auto` that is only a brand whose name is also an everyday word, such as `Vault`; with `icons=off` it is every match. | The exact `icon=provider:name` to add. | | `W205` | A group has no `type=`, and its name matches exactly one group type, such as `Subnet` or `App Service Plan`. | The exact `type=` to add, with `provider=` when the name names one provider. | - A phrase counts only as whole words, so `Slambda` is not Lambda. - The longest matching phrase wins: `Azure Key Vault` beats `Key Vault`. - A label that fits two products, or two group types, gets no suggestion. - A generic phrase such as `SQL database` counts only inside a group, or a diagram, whose provider owns that product. - A technology brand such as `Redis` counts only when it is the whole label. - A `decision`, `terminal`, or `step` node, a phrase shorter than three characters, and a provider name on its own never get a suggestion. ## See also | Doc | Why | |---|---| | [Language reference](https://docs.plotwork.dev/reference/language.md) | Every page of the reference and what each covers | | [Diagnostic codes](https://docs.plotwork.dev/reference/language/diagnostics.md) | Every code, including the density warnings | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The bounds each tool applies to its own input | | [How a diagram is drawn](https://docs.plotwork.dev/how-it-works.md) | What layout does with a diagram that is large | --- # Diagnostic codes > Page: https://docs.plotwork.dev/reference/language/diagnostics. Index of every page: https://docs.plotwork.dev/llms.txt Every error and warning code with its severity and name, and how to read one. ## TL;DR | Question | Answer | |---|---| | What does the first letter mean? | `E` is an error and stops rendering, `W` is a warning and never blocks it, `M` comes from the Mermaid converter, `I` from the importer, `R` from a team's style rules, and `S` from the repository settings. | | What does a diagnostic carry? | A code, a line, a column, a message, and a hint. Every one is reported in a single pass, so fix them all and check again. | | How many codes are there? | 64 errors, 40 warnings, 53 Mermaid conversion codes, 12 import codes, 10 style rule codes, and 9 settings codes. | | Where do the Mermaid and import codes point? | Into the Mermaid or the file you gave, not into Plotwork source. | ## Reading a diagnostic Codes starting with E are errors and stop rendering. Codes starting with W are warnings and never block it. Codes starting with M come from the Mermaid converter and point into the Mermaid, not into Plotwork source: M1xx codes are errors that stop a conversion, and M2xx codes are warnings about something dropped or changed. Each is explained in [Migrating from Mermaid](https://docs.plotwork.dev/migrating-from-mermaid.md). ## Errors | Code | Severity | Name | |---|---|---| | `E100` | error | `syntax` | | `E101` | error | `unterminatedString` | | `E110` | error | `unknownStatement` | | `E111` | error | `missingHeader` | | `E120` | error | `unknownNodeType` | | `E121` | error | `unknownTone` | | `E122` | error | `unknownIcon` | | `E123` | error | `unknownDirection` | | `E124` | error | `unknownGroupType` | | `E125` | error | `unknownProvider` | | `E130` | error | `duplicateId` | | `E131` | error | `unknownNode` | | `E132` | error | `unknownGroup` | | `E133` | error | `unclosedBlock` | | `E134` | error | `unexpectedClose` | | `E135` | error | `unknownOption` | | `E136` | error | `badOptionValue` | | `E137` | error | `unknownMember` | | `E138` | error | `badOverlay` | | `E139` | error | `overlayConflict` | | `E140` | error | `tooComplex` | | `E300` | error | `gitMergeIntoSelf` | | `E301` | error | `gitNoCommitYet` | | `E302` | error | `gitAlreadyPicked` | | `E303` | error | `gitTooManyTags` | | `E304` | error | `gitNoCommits` | | `E340` | error | `unknownClassType` | | `E341` | error | `badMember` | | `E342` | error | `unknownRelation` | | `E343` | error | `badCardinality` | | `E344` | error | `unsupportedArrow` | | `E345` | error | `packageTooDeep` | | `E360` | error | `stateReservedId` | | `E361` | error | `stateMarkerDirection` | | `E362` | error | `stateMixedRegions` | | `E363` | error | `stateRegionOutside` | | `E364` | error | `statePseudoLoop` | | `E365` | error | `stateContainedTransition` | | `E366` | error | `stateHistoryOutside` | | `E367` | error | `stateNoteOnMarker` | | `E400` | error | `journeyNoScore` | | `E401` | error | `journeyBadScore` | | `E402` | error | `journeyEmpty` | | `E403` | error | `journeyEmptyName` | | `E420` | error | `mindmapMissingRoot` | | `E421` | error | `mindmapDuplicateRoot` | | `E422` | error | `mindmapTooDeep` | | `E423` | error | `mindmapEmptyLabel` | | `E424` | error | `mindmapRootBlock` | | `E320` | error | `ganttBadDate` | | `E321` | error | `ganttBadDuration` | | `E322` | error | `ganttNoStart` | | `E323` | error | `ganttNoLength` | | `E324` | error | `ganttConflict` | | `E325` | error | `ganttCycle` | | `E326` | error | `ganttEndsBeforeStart` | | `E327` | error | `ganttMilestoneSpan` | | `E328` | error | `ganttEmpty` | | `E329` | error | `ganttBadRange` | | `E330` | error | `ganttNestedSection` | | `E380` | error | `timelineEmpty` | | `E381` | error | `timelineEventOutsidePeriod` | | `E382` | error | `timelineMisplaced` | | `E383` | error | `timelineEmptyLabel` | ## Warnings | Code | Severity | Name | |---|---|---| | `W200` | warning | `tooDense` | | `W201` | warning | `orphanNode` | | `W202` | warning | `tooWide` | | `W203` | warning | `textShortened` | | `W204` | warning | `suggestIcon` | | `W205` | warning | `suggestGroupType` | | `W206` | warning | `overlayCovers` | | `W207` | warning | `iconArtMissing` | | `W500` | warning | `suggestLineRole` | | `W300` | warning | `gitRedundantMerge` | | `W301` | warning | `gitEmptyBranch` | | `W302` | warning | `gitDuplicateTag` | | `W303` | warning | `gitLongFork` | | `W320` | warning | `bigClass` | | `W321` | warning | `inheritanceCycle` | | `W322` | warning | `implementsClass` | | `W323` | warning | `duplicateRelation` | | `W330` | warning | `stateUnreachable` | | `W331` | warning | `stateDeadEnd` | | `W332` | warning | `stateChoiceBranches` | | `W333` | warning | `stateBarBranches` | | `W334` | warning | `stateDeepNesting` | | `W350` | warning | `journeyFlat` | | `W351` | warning | `journeyActorTones` | | `W352` | warning | `journeyUnusedActor` | | `W353` | warning | `journeyLookalikeActors` | | `W360` | warning | `mindmapBare` | | `W361` | warning | `mindmapDeep` | | `W362` | warning | `mindmapManyBranches` | | `W363` | warning | `mindmapCrowdedFan` | | `W364` | warning | `mindmapRepeatedLabel` | | `W310` | warning | `ganttTodayOutside` | | `W311` | warning | `ganttStartMoved` | | `W312` | warning | `ganttOverdue` | | `W313` | warning | `ganttDoneInFuture` | | `W314` | warning | `ganttUngrouped` | | `W340` | warning | `timelineEmptyPeriod` | | `W341` | warning | `timelineOutOfOrder` | | `W342` | warning | `timelineDuplicatePeriod` | | `W343` | warning | `timelineEmptySection` | ## Mermaid conversion codes | Code | Severity | Name | |---|---|---| | `M100` | error | `mermaidNotDiagram` | | `M101` | error | `mermaidUnsupportedType` | | `M102` | error | `mermaidTooLarge` | | `M103` | error | `mermaidEmpty` | | `M104` | error | `mermaidInvalidOutput` | | `M110` | error | `mermaidUnreadable` | | `M200` | warning | `mermaidDroppedStyling` | | `M201` | warning | `mermaidDroppedInteraction` | | `M202` | warning | `mermaidDroppedLayoutHint` | | `M203` | warning | `mermaidDroppedMeaning` | | `M204` | warning | `mermaidMappedToNearest` | | `M205` | warning | `mermaidLabelChanged` | | `M206` | warning | `mermaidIdRenamed` | | `M207` | warning | `mermaidTypeFromHint` | | `M208` | warning | `mermaidNeutralType` | | `M209` | warning | `mermaidDuplicateDropped` | | `M210` | warning | `mermaidSubgraphEdge` | | `M211` | warning | `mermaidColumnAdded` | | `M212` | warning | `mermaidJunctionAdded` | | `M213` | warning | `mermaidNodeInManyGroups` | | `M214` | warning | `mermaidNoTitle` | | `M215` | warning | `mermaidIconToken` | | `M216` | warning | `mermaidEmptyElement` | | `M217` | warning | `mermaidRepairedBlock` | | `M218` | warning | `mermaidSkippedStatement` | | `M120` | error | `mermaidGitInconsistent` | | `M220` | warning | `mermaidGitGeneratedIds` | | `M221` | warning | `mermaidGitParentDropped` | | `M222` | warning | `mermaidGitMessageDropped` | | `M240` | warning | `mermaidClassMemberRewritten` | | `M241` | warning | `mermaidClassRelationApproximated` | | `M242` | warning | `mermaidClassStereotype` | | `M250` | warning | `mermaidStateNoteInside` | | `M251` | warning | `mermaidStateReservedId` | | `M270` | warning | `mermaidJourneyScoreAdjusted` | | `M271` | warning | `mermaidJourneyActorsChanged` | | `M180` | error | `mermaidMindmapManyRoots` | | `M280` | warning | `mermaidMindmapShape` | | `M281` | warning | `mermaidMindmapIcon` | | `M282` | warning | `mermaidMindmapIdText` | | `M130` | error | `mermaidGanttTime` | | `M131` | error | `mermaidGanttUnresolved` | | `M132` | error | `mermaidGanttDateFormat` | | `M230` | warning | `mermaidGanttAxisFormat` | | `M231` | warning | `mermaidGanttExcludes` | | `M232` | warning | `mermaidGanttTickInterval` | | `M233` | warning | `mermaidGanttToday` | | `M234` | warning | `mermaidGanttMilestone` | | `M235` | warning | `mermaidGanttDuration` | | `M236` | warning | `mermaidGanttTag` | | `M237` | warning | `mermaidGanttEndDate` | | `M238` | warning | `mermaidGanttSetting` | | `M260` | warning | `mermaidTimelineComment` | ## Import codes Codes starting with I come from `import_source` and point into the file you imported. I1xx codes are errors and nothing was imported; I2xx codes are warnings about something left out or changed, and the import went on. Each is explained in [Importing from code and data](https://docs.plotwork.dev/importing-from-code-and-data.md). | Code | Severity | Name | |---|---|---| | `I100` | error | `importEmpty` | | `I101` | error | `importUnreadable` | | `I102` | error | `importTooLarge` | | `I103` | error | `importInvalidOutput` | | `I104` | error | `importNotFound` | | `I105` | error | `importNoShape` | | `I200` | warning | `importDropped` | | `I201` | warning | `importSkipped` | | `I202` | warning | `importUnresolved` | | `I203` | warning | `importChanged` | | `I204` | warning | `importSplit` | | `I206` | warning | `importPartial` | ## Style rule codes Codes starting with R come from a team's style rules, checked by `plotwork check` and the GitHub Action. R1xx codes point into the settings or an organisation's saved rules and stop the check; R2xx codes point into the diagram and are errors unless the rule says `"level": "warning"`. Each is explained in [Repository settings](https://docs.plotwork.dev/repository-settings.md). | Code | Severity | Name | |---|---|---| | `R100` | error | `rulesInvalid` | | `R101` | error | `rulesBadPattern` | | `R200` | error | `ruleDescription` | | `R201` | error | `ruleMaxNodes` | | `R202` | error | `ruleKind` | | `R203` | error | `ruleIdName` | | `R204` | error | `ruleLabelName` | | `R205` | error | `ruleIcon` | | `R206` | error | `ruleTone` | | `R207` | error | `ruleTitle` | ## Settings codes Codes starting with S come from the repository settings (`plotwork.config.json`) and the diagrams `generate` makes from its sources. S100 points into the settings and stops the run; S200 is a warning that a default the header does not take was left out. S101 to S104 stop one generated diagram: a source the run cannot use, an overrides file that does not check, a source whose files match nothing, and a Markdown fence that is not there. S201 warns of an override whose target the import no longer draws, and S202 of one the diagram could not take. | Code | Severity | Name | |---|---|---| | `S100` | error | `settingsInvalid` | | `S200` | warning | `settingIgnored` | | `S101` | error | `sourceInvalid` | | `S102` | error | `overridesInvalid` | | `S103` | error | `sourceNoFiles` | | `S104` | error | `fenceMissing` | | `S201` | warning | `overrideStale` | | `S202` | warning | `overrideRefused` | | `S203` | warning | `overridesMissing` | ## See also | Doc | Why | |---|---| | [Language reference](https://docs.plotwork.dev/reference/language.md) | Every page of the reference and what each covers | | [Limits and quality checks](https://docs.plotwork.dev/reference/language/limits.md) | The density warnings and the two suggestions | | [Migrating from Mermaid](https://docs.plotwork.dev/migrating-from-mermaid.md) | What the M codes mean and what to do about each | | [Importing from code and data](https://docs.plotwork.dev/importing-from-code-and-data.md) | What the I codes mean and what to do about each | | [Repository settings](https://docs.plotwork.dev/repository-settings.md) | What the R and S codes mean, and how a team sets its defaults and rules | | [Writing with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The fix-and-check loop an agent runs on these codes | --- # Icon reference > Page: https://docs.plotwork.dev/reference/icons. Index of every page: https://docs.plotwork.dev/llms.txt Every bundled icon, grouped by what it is for, and how to find the right one. ## TL;DR | Question | Answer | |---|---| | How do I use an icon? | Add `icon=<set>:<name>` to a node, actor, table, or group, for example `icon=lucide:bug`. A group shows its icon in its corner tab. | | How many are there? | 2283, in 12 sets: `lucide` (general), `simple` (technology and brand logos), `status` (colored outcomes), and one set for each cloud provider (`aws`, `gcp`, `azure`, `alibaba`, `oci`, `ibm`, `cloudflare`, `digitalocean`, `vercel`). | | How do I name a cloud product? | Write the provider, a colon, and the product: `aws:lambda`, `gcp:bigquery`, `azure:functions`, `alibaba:oss`. The provider itself is `<provider>:cloud`. | | How do I find one? | Call the `search_icons` tool, or `map_icons` for many names at once. Search reads names, product titles, abbreviations, tags, and the ideas listed below, so `lambda`, `gke`, `cosmos db`, `token exchange`, and `validate` all work. | | What if I mistype a name? | The diagram fails with an error and the closest valid names. A guessed provider such as `amazon:s3` is pointed at `aws:s3`. | | Which icons keep their own color? | The `status` set, and the official AWS, Azure, and Google Cloud artwork, which is drawn as its owner published it. Every other icon takes the color of the node or group that holds it. | | Which names does a cloud architecture need? | Search the product name from the request, for example `cosmos db`, and use the first result. The [cloud architecture guide](https://docs.plotwork.dev/cloud-architecture.md) explains how, and the icon pages draw every AWS, Azure, and Google Cloud product: [AWS](https://docs.plotwork.dev/cloud-icons/aws.md), [Azure](https://docs.plotwork.dev/cloud-icons/azure.md), and [Google Cloud](https://docs.plotwork.dev/cloud-icons/google-cloud.md). | ## Gallery *Picture: Gallery of all 2283 bundled icons, in groups, each tile showing the icon and its name.* ## Cloud and platform icons Each provider has its own set, so a product is found by the name its provider gives it. Where a provider publishes an official icon for a product, the icon draws it as published. Where it has a licensed brand mark, the icon draws that. Every other product draws the glyph of its kind of service (a function, a queue, object storage), so the same kind of service looks the same across providers and the label says whose it is. Put the provider's own icon on the group that holds its resources: `group aws "AWS" icon=aws:cloud {`, then its nodes, then `}`. ### Find a product Search matches a product by its name, its official title, and its abbreviations, with or without the provider in front of it. A phrase for a kind of service, such as `message queue`, finds every provider's product of that kind, most used provider first. The first result for each query below comes from the same search the tools use. | Query | First result | |---|---| | `lambda` | `aws:lambda` | | `serverless function` | `aws:lambda` | | `cloud functions` | `gcp:cloud-functions` | | `azure functions` | `azure:functions` | | `s3` | `aws:s3` | | `bucket` | `aws:s3` | | `gcs` | `gcp:cloud-storage` | | `blob storage` | `azure:blob-storage` | | `gke` | `gcp:gke` | | `eks` | `aws:eks` | | `aks` | `azure:aks` | | `kubernetes` | `simple:kubernetes` | | `bigquery` | `gcp:bigquery` | | `redshift` | `aws:redshift` | | `synapse` | `azure:synapse` | | `cosmos db` | `azure:cosmos-db` | | `dynamodb` | `aws:dynamodb` | | `firestore` | `gcp:firestore` | | `alibaba oss` | `alibaba:oss` | | `aliyun` | `alibaba:cloud` | | `message queue` | `aws:sqs` | | `google cloud` | `gcp:cloud` | ### Kinds of service Each kind has one glyph. These phrases find the products of a kind. | Kind of service | Phrases that find it | |---|---| | Provider | `cloud`, `cloud provider`, `provider`, `logo` | | Virtual machine | `virtual machine`, `virtual machines`, `vm`, `vms`, `virtual server` | | Serverless function | `serverless`, `serverless function`, `serverless functions`, `function`, `functions` | | Container service | `container`, `containers`, `serverless container`, `container service`, `docker` | | Managed Kubernetes | `kubernetes`, `k8s`, `managed kubernetes`, `kubernetes cluster`, `kubernetes service` | | Container registry | `container registry`, `registry`, `image registry`, `docker registry` | | App platform | `app platform`, `paas`, `platform as a service`, `web app`, `app hosting` | | Object storage | `object storage`, `bucket`, `buckets`, `storage bucket`, `object store` | | Block storage | `block storage`, `disk`, `volume`, `disks`, `volumes` | | File storage | `file storage`, `file system`, `shared file system`, `nfs`, `network file system` | | Relational database | `relational database`, `sql`, `sql database`, `rdbms`, `managed database` | | NoSQL database | `nosql`, `nosql database`, `document database`, `key value`, `key value store` | | In-memory cache | `cache`, `in memory cache`, `in memory`, `redis`, `memcached` | | Data warehouse | `data warehouse`, `warehouse`, `olap`, `data lakehouse` | | Analytics | `analytics`, `big data`, `data processing`, `etl`, `data pipeline` | | Message queue | `queue`, `message queue`, `queues`, `messaging`, `message broker` | | Event stream | `stream`, `streams`, `streaming`, `event stream`, `event streaming` | | Publish and subscribe | `publish subscribe`, `publish and subscribe`, `event bus`, `event router`, `topic` | | Workflow | `workflow`, `workflows`, `orchestration`, `state machine`, `orchestrator` | | API gateway | `api gateway`, `gateway`, `api management`, `api proxy`, `api` | | Load balancer | `load balancer`, `load balancing`, `balancer`, `lb`, `elb` | | Content delivery network | `cdn`, `content delivery`, `content delivery network`, `edge cache`, `edge network` | | DNS | `dns`, `domain name`, `domain name system`, `name server`, `nameserver` | | Virtual network | `vpc`, `virtual network`, `virtual private cloud`, `network`, `subnet` | | Firewall | `firewall`, `waf`, `web application firewall`, `ddos`, `ddos protection` | | Identity and access | `identity`, `iam`, `identity and access`, `identity and access management`, `access management` | | Key management | `key management`, `kms`, `encryption keys`, `key vault`, `hsm` | | Secrets | `secrets`, `secret manager`, `secrets manager`, `secret store`, `vault` | | Monitoring | `monitoring`, `metrics`, `observability`, `alerts`, `alerting` | | Logging | `logging`, `logs`, `log`, `log management`, `audit log` | | CI and CD | `ci cd`, `cicd`, `ci`, `cd`, `pipeline` | | Machine learning | `machine learning`, `ml`, `ai`, `artificial intelligence`, `model training` | | Email | `email`, `mail`, `transactional email`, `email service`, `smtp` | | Search | `search`, `full text search`, `search engine`, `search index`, `elasticsearch` | | Other product | | ### Providers with their own pages AWS, Azure, and Google Cloud have hundreds of products each, so each has a page that draws every icon by the categories its provider uses, with the exact name and the other names that find it. | Provider | Set | Icons | Page | |---|---|---|---| | Amazon Web Services | `aws:` | 826 | [Amazon Web Services icons](https://docs.plotwork.dev/cloud-icons/aws.md) | | Microsoft Azure | `azure:` | 669 | [Microsoft Azure icons](https://docs.plotwork.dev/cloud-icons/azure.md) | | Google Cloud | `gcp:` | 79 | [Google Cloud icons](https://docs.plotwork.dev/cloud-icons/google-cloud.md) | ### Other providers The six providers below are short lists, shown here with each icon drawn. The [other provider icons](https://docs.plotwork.dev/cloud-icons/other-providers.md) page groups the same icons by kind of service. ### Alibaba Cloud (alibaba) The provider draws its mark from Simple Icons (CC0). The products draw the glyph of their kind of service. | Icon | Product | Kind of service | Artwork | |---|---|---|---| | `alibaba:cloud` | Alibaba Cloud | Provider | Brand mark (Simple Icons) | | `alibaba:ecs` | Elastic Compute Service | Virtual machine | Glyph for the kind | | `alibaba:function-compute` | Function Compute | Serverless function | Glyph for the kind | | `alibaba:ack` | Container Service for Kubernetes | Managed Kubernetes | Glyph for the kind | | `alibaba:oss` | Object Storage Service | Object storage | Glyph for the kind | | `alibaba:rds` | ApsaraDB RDS | Relational database | Glyph for the kind | | `alibaba:polardb` | PolarDB | Relational database | Glyph for the kind | | `alibaba:tair` | Tair (ApsaraDB for Redis) | In-memory cache | Glyph for the kind | | `alibaba:maxcompute` | MaxCompute | Data warehouse | Glyph for the kind | | `alibaba:rocketmq` | ApsaraMQ for RocketMQ | Message queue | Glyph for the kind | | `alibaba:api-gateway` | API Gateway | API gateway | Glyph for the kind | | `alibaba:slb` | Server Load Balancer | Load balancer | Glyph for the kind | | `alibaba:cdn` | Alibaba Cloud CDN | Content delivery network | Glyph for the kind | | `alibaba:alidns` | Alibaba Cloud DNS | DNS | Glyph for the kind | | `alibaba:vpc` | Virtual Private Cloud | Virtual network | Glyph for the kind | | `alibaba:ram` | Resource Access Management | Identity and access | Glyph for the kind | | `alibaba:sls` | Log Service | Logging | Glyph for the kind | | `alibaba:pai` | Platform for AI | Machine learning | Glyph for the kind | ### Oracle Cloud Infrastructure (oci) Simple Icons removed the Oracle marks over trademark permission. Every OCI icon draws the glyph of its kind of service. | Icon | Product | Kind of service | Artwork | |---|---|---|---| | `oci:cloud` | Oracle Cloud Infrastructure | Provider | Glyph for the kind | | `oci:compute` | OCI Compute | Virtual machine | Glyph for the kind | | `oci:functions` | OCI Functions | Serverless function | Glyph for the kind | | `oci:oke` | Container Engine for Kubernetes | Managed Kubernetes | Glyph for the kind | | `oci:object-storage` | OCI Object Storage | Object storage | Glyph for the kind | | `oci:autonomous-database` | Oracle Autonomous Database | Relational database | Glyph for the kind | | `oci:load-balancer` | OCI Load Balancer | Load balancer | Glyph for the kind | | `oci:vcn` | Virtual Cloud Network | Virtual network | Glyph for the kind | | `oci:streaming` | OCI Streaming | Event stream | Glyph for the kind | ### IBM Cloud (ibm) The icons are IBM Carbon pictograms, which IBM publishes under Apache-2.0. Products without a pictogram draw the glyph of their kind of service. | Icon | Product | Kind of service | Artwork | |---|---|---|---| | `ibm:cloud` | IBM Cloud | Provider | Provider pictogram (Carbon) | | `ibm:code-engine` | IBM Cloud Code Engine | Container service | Provider pictogram (Carbon) | | `ibm:kubernetes-service` | IBM Cloud Kubernetes Service | Managed Kubernetes | Provider pictogram (Carbon) | | `ibm:cloudant` | IBM Cloudant | NoSQL database | Provider pictogram (Carbon) | | `ibm:db2` | IBM Db2 | Relational database | Provider pictogram (Carbon) | | `ibm:event-streams` | IBM Event Streams | Event stream | Provider pictogram (Carbon) | | `ibm:mq` | IBM MQ | Message queue | Provider pictogram (Carbon) | | `ibm:key-protect` | IBM Key Protect | Key management | Provider pictogram (Carbon) | | `ibm:secrets-manager` | IBM Secrets Manager | Secrets | Provider pictogram (Carbon) | | `ibm:app-id` | IBM App ID | Identity and access | Provider pictogram (Carbon) | | `ibm:vpc` | IBM Cloud VPC | Virtual network | Provider pictogram (Carbon) | | `ibm:api-connect` | IBM API Connect | API gateway | Provider pictogram (Carbon) | | `ibm:watsonx` | IBM watsonx.ai | Machine learning | Provider pictogram (Carbon) | ### Cloudflare (cloudflare) The provider, Workers, and Pages draw their marks from Simple Icons (CC0). Cloudflare publishes no icon pack that may be bundled, so every other product draws a glyph: the glyph of its kind of service, or a Carbon glyph (Apache-2.0) where one fits the product better. The label says whose it is. | Icon | Product | Kind of service | Artwork | |---|---|---|---| | `cloudflare:cloud` | Cloudflare | Provider | Brand mark (Simple Icons) | | `cloudflare:workers` | Workers | Serverless function | Brand mark (Simple Icons) | | `cloudflare:pages` | Cloudflare Pages | App platform | Brand mark (Simple Icons) | | `cloudflare:r2` | Cloudflare R2 | Object storage | Glyph for the kind | | `cloudflare:d1` | Cloudflare D1 | Relational database | Glyph for the kind | | `cloudflare:kv` | Workers KV | NoSQL database | Glyph for the kind | | `cloudflare:queues` | Cloudflare Queues | Message queue | Glyph for the kind | | `cloudflare:durable-objects` | Durable Objects | Serverless function | Glyph of its own (Carbon) | | `cloudflare:workers-ai` | Workers AI | Machine learning | Glyph for the kind | | `cloudflare:workflows` | Cloudflare Workflows | Workflow | Glyph for the kind | | `cloudflare:vectorize` | Vectorize | Search | Glyph of its own (Carbon) | | `cloudflare:hyperdrive` | Hyperdrive | In-memory cache | Glyph of its own (Carbon) | | `cloudflare:ai-gateway` | Cloudflare AI Gateway | API gateway | Glyph for the kind | | `cloudflare:images` | Cloudflare Images | Content delivery network | Glyph of its own (Carbon) | | `cloudflare:stream` | Cloudflare Stream | Content delivery network | Glyph of its own (Carbon) | | `cloudflare:turnstile` | Turnstile | Firewall | Glyph of its own (Carbon) | | `cloudflare:tunnel` | Cloudflare Tunnel | Virtual network | Glyph of its own (Carbon) | | `cloudflare:load-balancing` | Cloudflare Load Balancing | Load balancer | Glyph for the kind | | `cloudflare:rate-limiting` | Cloudflare Rate Limiting | Firewall | Glyph of its own (Carbon) | | `cloudflare:email-routing` | Cloudflare Email Routing | Email | Glyph for the kind | | `cloudflare:analytics-engine` | Workers Analytics Engine | Analytics | Glyph for the kind | | `cloudflare:logpush` | Logpush | Logging | Glyph for the kind | | `cloudflare:containers` | Cloudflare Containers | Container service | Glyph for the kind | | `cloudflare:browser-rendering` | Cloudflare Browser Rendering | Serverless function | Glyph of its own (Carbon) | | `cloudflare:zero-trust` | Cloudflare Zero Trust | Identity and access | Glyph for the kind | | `cloudflare:access` | Cloudflare Access | Identity and access | Glyph for the kind | | `cloudflare:gateway` | Cloudflare Gateway | Firewall | Glyph of its own (Carbon) | | `cloudflare:spectrum` | Cloudflare Spectrum | Virtual network | Glyph of its own (Carbon) | | `cloudflare:argo-smart-routing` | Argo Smart Routing | Virtual network | Glyph of its own (Carbon) | | `cloudflare:magic-transit` | Magic Transit | Virtual network | Glyph of its own (Carbon) | | `cloudflare:cdn` | Cloudflare CDN | Content delivery network | Glyph for the kind | | `cloudflare:cache` | Cloudflare Cache | In-memory cache | Glyph for the kind | | `cloudflare:dns` | Cloudflare DNS | DNS | Glyph for the kind | | `cloudflare:registrar` | Cloudflare Registrar | DNS | Glyph of its own (Carbon) | | `cloudflare:custom-domains` | Workers Custom Domains | DNS | Glyph of its own (Carbon) | | `cloudflare:waf` | Cloudflare WAF | Firewall | Glyph for the kind | | `cloudflare:ddos-protection` | Cloudflare DDoS Protection | Firewall | Glyph for the kind | | `cloudflare:bot-management` | Cloudflare Bot Management | Firewall | Glyph for the kind | | `cloudflare:transform-rules` | Cloudflare Transform Rules | Content delivery network | Glyph of its own (Carbon) | | `cloudflare:cron-triggers` | Workers Cron Triggers | Workflow | Glyph of its own (Carbon) | | `cloudflare:service-bindings` | Workers Service Bindings | Virtual network | Glyph of its own (Carbon) | | `cloudflare:static-assets` | Workers Static Assets | Content delivery network | Glyph of its own (Carbon) | ### DigitalOcean (digitalocean) The provider draws its mark from Simple Icons (CC0). The products draw the glyph of their kind of service. | Icon | Product | Kind of service | Artwork | |---|---|---|---| | `digitalocean:cloud` | DigitalOcean | Provider | Brand mark (Simple Icons) | | `digitalocean:droplets` | Droplets | Virtual machine | Glyph for the kind | | `digitalocean:app-platform` | App Platform | App platform | Glyph for the kind | | `digitalocean:doks` | DigitalOcean Kubernetes | Managed Kubernetes | Glyph for the kind | | `digitalocean:spaces` | Spaces Object Storage | Object storage | Glyph for the kind | | `digitalocean:volumes` | Volumes Block Storage | Block storage | Glyph for the kind | | `digitalocean:managed-databases` | Managed Databases | Relational database | Glyph for the kind | | `digitalocean:load-balancers` | Load Balancers | Load balancer | Glyph for the kind | ### Vercel (vercel) The provider draws its mark from Simple Icons (CC0). The products draw the glyph of their kind of service. | Icon | Product | Kind of service | Artwork | |---|---|---|---| | `vercel:cloud` | Vercel | Provider | Brand mark (Simple Icons) | | `vercel:functions` | Vercel Functions | Serverless function | Glyph for the kind | | `vercel:edge-network` | Vercel Edge Network | Content delivery network | Glyph for the kind | | `vercel:blob` | Vercel Blob | Object storage | Glyph for the kind | ## Find an icon by idea Search matches these phrases. Any word in a phrase finds the icons listed beside it, best fit first. | Idea | Icons | |---|---| | token exchange swap | `lucide:arrow-left-right`, `lucide:arrow-right-left`, `lucide:repeat-2`, `lucide:replace`, `lucide:git-compare-arrows` | | token jwt bearer credential | `lucide:ticket`, `lucide:key-round`, `lucide:badge`, `lucide:file-key` | | validate validation check | `lucide:badge-check`, `lucide:circle-check`, `lucide:list-checks`, `lucide:clipboard-check`, `lucide:square-check`, `lucide:search-check` | | verify verified verification | `lucide:badge-check`, `lucide:shield-check`, `lucide:scan-search`, `lucide:search-check` | | authenticate authentication login sign-in | `lucide:log-in`, `lucide:fingerprint-pattern`, `lucide:user-check`, `lucide:id-card`, `lucide:key-round` | | authorize authorization permission access grant | `lucide:shield-check`, `lucide:key-round`, `lucide:lock-open`, `lucide:ticket-check` | | deny denied reject block blocked | `lucide:shield-x`, `lucide:ban`, `lucide:circle-x`, `lucide:octagon-x` | | delegate delegation behalf | `lucide:handshake`, `lucide:users-round`, `lucide:forward` | | revoke revoked revocation | `lucide:ban`, `lucide:shield-off`, `lucide:ticket-x`, `lucide:user-x` | | refresh renew rotate rotation | `lucide:refresh-cw`, `lucide:refresh-ccw-dot`, `lucide:rotate-cw`, `lucide:repeat-2` | | expire expired timeout ttl | `lucide:clock-alert`, `lucide:hourglass`, `lucide:timer`, `lucide:calendar-clock` | | consent approve approval sign | `lucide:signature`, `lucide:stamp`, `lucide:badge-check`, `lucide:pen-line` | | policy rule rules compliance | `lucide:scale`, `lucide:gavel`, `lucide:scroll-text`, `lucide:list-checks` | | audit log trail record | `lucide:clipboard-list`, `lucide:scroll-text`, `lucide:file-search`, `lucide:receipt-text` | | secret password key vault | `lucide:lock-keyhole`, `lucide:key-square`, `lucide:file-key`, `lucide:eye-off` | | bug defect issue | `lucide:bug`, `lucide:bug-play`, `lucide:bug-off` | | error failure fail failed | `lucide:circle-x`, `lucide:octagon-x`, `lucide:triangle-alert`, `lucide:octagon-alert`, `lucide:skull` | | warning caution risk | `lucide:triangle-alert`, `lucide:octagon-alert`, `lucide:siren` | | success ok done pass passed | `lucide:circle-check`, `lucide:check-check`, `lucide:badge-check`, `lucide:square-check` | | retry again repeat | `lucide:rotate-ccw`, `lucide:repeat`, `lucide:refresh-cw` | | agent ai llm model assistant | `lucide:bot`, `lucide:brain-circuit`, `lucide:sparkles`, `lucide:cpu` | | mcp tool plugin extension | `lucide:plug`, `lucide:plug-zap`, `lucide:puzzle`, `lucide:wand-sparkles` | | api endpoint route | `lucide:webhook`, `lucide:network`, `lucide:code-xml`, `lucide:cable`, `lucide:route` | | webhook callback event | `lucide:webhook`, `lucide:radio`, `lucide:bell-ring` | | deploy release ship | `lucide:rocket`, `lucide:package-check`, `lucide:container` | | monitor observe metrics alert | `lucide:activity`, `lucide:gauge`, `lucide:scan-eye`, `lucide:siren` | | cache memory | `lucide:database-zap`, `lucide:memory-stick` | | storage bucket disk | `lucide:hard-drive`, `lucide:archive`, `lucide:folder` | | user human person | `lucide:user`, `lucide:user-round`, `lucide:circle-user` | | team group | `lucide:users`, `lucide:users-round` | | branch fork decision choice | `lucide:split`, `lucide:git-branch`, `lucide:route` | | merge join combine | `lucide:merge`, `lucide:git-merge` | | notify notification email send | `lucide:send`, `lucide:mail`, `lucide:bell`, `lucide:megaphone` | | config configuration settings | `lucide:settings`, `lucide:settings-2`, `lucide:sliders-horizontal`, `lucide:toggle-right` | | search find lookup query | `lucide:search`, `lucide:search-check`, `lucide:scan-search` | | time schedule clock | `lucide:clock`, `lucide:calendar-clock`, `lucide:timer` | | network internet web | `lucide:globe`, `lucide:network`, `lucide:wifi`, `lucide:cloud` | | offline outage down | `lucide:cloud-off`, `lucide:wifi-off`, `lucide:server-crash`, `lucide:zap-off` | | boundary trust zone | `lucide:shield`, `lucide:globe-lock`, `lucide:folder-lock`, `lucide:lock` | | session cookie | `lucide:id-card`, `lucide:clock`, `lucide:user-check` | | scope claims | `lucide:badge`, `lucide:list-checks`, `lucide:key-square` | ## Status icons | Icon | Color | Meaning | |---|---|---| | `status:yes` | green | An affirmative answer or a passed check; shown on a branch labelled yes. | | `status:no` | red | A negative answer or a failed check; shown on a branch labelled no. | | `status:success` | green | Succeeded, passed, or done. | | `status:error` | red | Failed or rejected. | | `status:warning` | amber | Needs attention but did not fail. | | `status:info` | blue | Informational note. | | `status:pending` | neutral | Waiting for something. | | `status:running` | blue | In progress. | | `status:retry` | amber | Will be tried again. | | `status:skipped` | neutral | Intentionally not run. | | `status:proceed` | green | Allowed to continue to the next step. | | `status:allowed` | green | Access or a request was granted. | | `status:denied` | red | Access or a request was refused. | | `status:blocked` | red | Stopped by a rule or policy. | | `status:bug` | red | A defect or known issue. | | `status:verified` | green | Checked and confirmed, such as a verified token or identity. | | `status:expired` | amber | No longer valid because time ran out. | | `status:revoked` | red | Taken back on purpose; a revoked grant or token. | | `status:up` | green | Increase or improvement. | | `status:down` | red | Decrease or regression. | ## General icons by category | Category | Count | Icons | |---|---|---| | People | 13 | `lucide:user`, `lucide:users`, `lucide:user-round`, `lucide:users-round`, `lucide:circle-user`, `lucide:user-check`, `lucide:user-x`, `lucide:user-cog`, `lucide:user-lock`, `lucide:user-plus`, `lucide:handshake`, `lucide:crown`, `lucide:graduation-cap` | | Agents and AI | 12 | `lucide:bot`, `lucide:bot-message-square`, `lucide:brain`, `lucide:brain-circuit`, `lucide:sparkles`, `lucide:wand-sparkles`, `lucide:cpu`, `lucide:memory-stick`, `lucide:message-square`, `lucide:message-circle`, `lucide:lightbulb`, `lucide:telescope` | | Devices and code | 14 | `lucide:monitor`, `lucide:monitor-smartphone`, `lucide:smartphone`, `lucide:laptop`, `lucide:terminal`, `lucide:square-terminal`, `lucide:globe`, `lucide:globe-lock`, `lucide:code`, `lucide:code-xml`, `lucide:braces`, `lucide:binary`, `lucide:file-code`, `lucide:package` | | Compute and network | 31 | `lucide:server`, `lucide:server-cog`, `lucide:server-crash`, `lucide:zap`, `lucide:network`, `lucide:router`, `lucide:cable`, `lucide:waypoints`, `lucide:workflow`, `lucide:webhook`, `lucide:container`, `lucide:box`, `lucide:boxes`, `lucide:blocks`, `lucide:package-check`, `lucide:package-x`, `lucide:cloud`, `lucide:cloud-upload`, `lucide:cloud-download`, `lucide:cloud-off`, `lucide:cloud-alert`, `lucide:plug`, `lucide:plug-zap`, `lucide:unplug`, `lucide:radio`, `lucide:satellite-dish`, `lucide:rocket`, `lucide:factory`, `lucide:puzzle`, `lucide:wifi`, `lucide:wifi-off` | | Data | 19 | `lucide:database`, `lucide:database-zap`, `lucide:database-backup`, `lucide:table`, `lucide:layers`, `lucide:hard-drive`, `lucide:warehouse`, `lucide:folder`, `lucide:folder-lock`, `lucide:file-text`, `lucide:file-key`, `lucide:file-lock`, `lucide:file-check`, `lucide:file-x`, `lucide:file-search`, `lucide:archive`, `lucide:clipboard-list`, `lucide:book-open`, `lucide:inbox` | | Identity and trust | 29 | `lucide:id-card`, `lucide:key`, `lucide:key-round`, `lucide:key-square`, `lucide:lock`, `lucide:lock-keyhole`, `lucide:lock-open`, `lucide:shield`, `lucide:shield-check`, `lucide:shield-alert`, `lucide:shield-x`, `lucide:shield-off`, `lucide:shield-ellipsis`, `lucide:shield-user`, `lucide:badge`, `lucide:badge-check`, `lucide:badge-alert`, `lucide:badge-x`, `lucide:fingerprint-pattern`, `lucide:ticket`, `lucide:ticket-check`, `lucide:ticket-x`, `lucide:scan-eye`, `lucide:scan-face`, `lucide:eye`, `lucide:eye-off`, `lucide:stamp`, `lucide:signature`, `lucide:pen-line` | | Tokens and exchange | 18 | `lucide:arrow-left-right`, `lucide:arrow-right-left`, `lucide:repeat`, `lucide:repeat-2`, `lucide:replace`, `lucide:refresh-ccw-dot`, `lucide:refresh-cw`, `lucide:refresh-ccw`, `lucide:rotate-cw`, `lucide:rotate-ccw`, `lucide:git-compare-arrows`, `lucide:forward`, `lucide:reply`, `lucide:share-2`, `lucide:merge`, `lucide:split`, `lucide:route`, `lucide:handshake` | | Validation and checks | 16 | `lucide:check`, `lucide:check-check`, `lucide:circle-check`, `lucide:circle-x`, `lucide:x`, `lucide:list-checks`, `lucide:clipboard-check`, `lucide:search-check`, `lucide:scan-search`, `lucide:scale`, `lucide:gavel`, `lucide:book-open-check`, `lucide:square-check`, `lucide:square-x`, `lucide:badge-check`, `lucide:target` | | Errors and warnings | 16 | `lucide:bug`, `lucide:bug-off`, `lucide:bug-play`, `lucide:triangle-alert`, `lucide:octagon-alert`, `lucide:octagon-x`, `lucide:circle-alert`, `lucide:ban`, `lucide:circle-slash`, `lucide:siren`, `lucide:flame`, `lucide:message-square-warning`, `lucide:clock-alert`, `lucide:skull`, `lucide:zap-off`, `lucide:x` | | Status and progress | 17 | `lucide:info`, `lucide:circle-question-mark`, `lucide:circle-play`, `lucide:circle-pause`, `lucide:circle-stop`, `lucide:circle-dot`, `lucide:circle`, `lucide:timer`, `lucide:hourglass`, `lucide:clock`, `lucide:calendar-clock`, `lucide:activity`, `lucide:gauge`, `lucide:pause`, `lucide:play`, `lucide:loader`, `lucide:loader-circle` | | Flow control | 23 | `lucide:arrow-right`, `lucide:arrow-left`, `lucide:arrow-up`, `lucide:arrow-down`, `lucide:arrow-up-right`, `lucide:arrow-down-right`, `lucide:circle-arrow-right`, `lucide:corner-down-right`, `lucide:chevrons-right`, `lucide:log-in`, `lucide:log-out`, `lucide:door-open`, `lucide:door-closed`, `lucide:undo-2`, `lucide:redo-2`, `lucide:git-branch`, `lucide:git-merge`, `lucide:git-pull-request`, `lucide:git-commit-horizontal`, `lucide:funnel`, `lucide:sliders-horizontal`, `lucide:toggle-left`, `lucide:toggle-right` | | Messaging | 14 | `lucide:send`, `lucide:mail`, `lucide:bell`, `lucide:bell-ring`, `lucide:megaphone`, `lucide:link`, `lucide:link-2`, `lucide:unlink`, `lucide:download`, `lucide:upload`, `lucide:copy`, `lucide:scroll-text`, `lucide:receipt`, `lucide:receipt-text` | | General | 17 | `lucide:settings`, `lucide:settings-2`, `lucide:cog`, `lucide:search`, `lucide:flag`, `lucide:bookmark`, `lucide:crosshair`, `lucide:map`, `lucide:map-pin`, `lucide:compass`, `lucide:house`, `lucide:square`, `lucide:calendar`, `lucide:credit-card`, `lucide:wallet`, `lucide:star`, `lucide:heart` | | Business and everyday | 24 | `lucide:shopping-cart`, `lucide:shopping-bag`, `lucide:store`, `lucide:tag`, `lucide:gift`, `lucide:truck`, `lucide:landmark`, `lucide:banknote`, `lucide:coins`, `lucide:piggy-bank`, `lucide:calculator`, `lucide:briefcase`, `lucide:building`, `lucide:headset`, `lucide:life-buoy`, `lucide:phone`, `lucide:pencil`, `lucide:square-pen`, `lucide:trash`, `lucide:image`, `lucide:printer`, `lucide:chart-line`, `lucide:chart-bar`, `lucide:chart-pie` | ## Technology and brand logos Brand logos come from Simple Icons and remain trademarks of their owners. A few full colour marks that Simple Icons does not publish, such as OpenAI, Slack, and Twilio, come from SVG Logos (CC0 1.0) and are drawn unmodified on a light tile. `simple:1password`, `simple:adyen`, `simple:airbyte`, `simple:airtable`, `simple:akamai`, `simple:algolia`, `simple:alibabacloud`, `simple:alpinedotjs`, `simple:alpinelinux`, `simple:amplitude`, `simple:anaconda`, `simple:android`, `simple:angular`, `simple:ansible`, `simple:anthropic`, `simple:apacheairflow`, `simple:apachecassandra`, `simple:apacheflink`, `simple:apachekafka`, `simple:apachemaven`, `simple:apachepulsar`, `simple:apachespark`, `simple:apachesuperset`, `simple:apple`, `simple:appwrite`, `simple:argo`, `simple:asana`, `simple:astro`, `simple:atlassian`, `simple:auth0`, `simple:authentik`, `simple:backstage`, `simple:betterauth`, `simple:betterstack`, `simple:biome`, `simple:bitwarden`, `simple:bluesky`, `simple:bun`, `simple:c`, `simple:caddy`, `simple:ceph`, `simple:chroma`, `simple:cilium`, `simple:circleci`, `simple:claude`, `simple:claudecode`, `simple:clerk`, `simple:clickhouse`, `simple:cline`, `simple:cloudflare`, `simple:cloudflarepages`, `simple:cloudflareworkers`, `simple:codex`, `simple:coinbase`, `simple:confluence`, `simple:consul`, `simple:containerd`, `simple:contentful`, `simple:convex`, `simple:couchbase`, `simple:cplusplus`, `simple:cursor`, `simple:cypress`, `simple:dart`, `simple:databricks`, `simple:datadog`, `simple:dbt`, `simple:debian`, `simple:deepgram`, `simple:deepseek`, `simple:deno`, `simple:devin`, `simple:dify`, `simple:digitalocean`, `simple:directus`, `simple:discord`, `simple:django`, `simple:docker`, `simple:dotnet`, `simple:drizzle`, `simple:dropbox`, `simple:duckdb`, `simple:dynatrace`, `simple:elasticsearch`, `simple:elevenlabs`, `simple:elixir`, `simple:envoyproxy`, `simple:erlang`, `simple:esbuild`, `simple:eslint`, `simple:etcd`, `simple:expo`, `simple:express`, `simple:facebook`, `simple:fastapi`, `simple:fastify`, `simple:fastly`, `simple:figma`, `simple:firebase`, `simple:firecrawl`, `simple:firefox`, `simple:flask`, `simple:fluentd`, `simple:flutter`, `simple:flydotio`, `simple:git`, `simple:github`, `simple:githubactions`, `simple:githubcopilot`, `simple:gitlab`, `simple:gmail`, `simple:go`, `simple:google`, `simple:googleanalytics`, `simple:googlebigquery`, `simple:googlebigtable`, `simple:googlechrome`, `simple:googlecloud`, `simple:googlecloudspanner`, `simple:googlecloudstorage`, `simple:googledataflow`, `simple:googledrive`, `simple:googlegemini`, `simple:googlepubsub`, `simple:gradle`, `simple:grafana`, `simple:graphql`, `simple:hasura`, `simple:helm`, `simple:heroku`, `simple:hetzner`, `simple:hono`, `simple:hotjar`, `simple:htmx`, `simple:hubspot`, `simple:huggingface`, `simple:influxdb`, `simple:instagram`, `simple:intercom`, `simple:istio`, `simple:jaeger`, `simple:javascript`, `simple:jenkins`, `simple:jest`, `simple:jetbrains`, `simple:jira`, `simple:jsonwebtokens`, `simple:jupyter`, `simple:k3s`, `simple:kaggle`, `simple:keycloak`, `simple:kibana`, `simple:klarna`, `simple:kong`, `simple:kotlin`, `simple:kubernetes`, `simple:langchain`, `simple:laravel`, `simple:launchdarkly`, `simple:letsencrypt`, `simple:linear`, `simple:linkedin`, `simple:linode`, `simple:linux`, `simple:livekit`, `simple:logstash`, `simple:macos`, `simple:mailchimp`, `simple:mailgun`, `simple:mariadb`, `simple:mastodon`, `simple:meilisearch`, `simple:memcached`, `simple:metabase`, `simple:microsoftteams`, `simple:milvus`, `simple:minio`, `simple:mistralai`, `simple:mixpanel`, `simple:modelcontextprotocol`, `simple:mongodb`, `simple:mui`, `simple:mysql`, `simple:n8n`, `simple:nats`, `simple:neo4j`, `simple:neon`, `simple:nestjs`, `simple:netlify`, `simple:newrelic`, `simple:nextdotjs`, `simple:nginx`, `simple:ngrok`, `simple:nodedotjs`, `simple:nomad`, `simple:notion`, `simple:npm`, `simple:nuxt`, `simple:nvidia`, `simple:nx`, `simple:okta`, `simple:ollama`, `simple:openai`, `simple:openid`, `simple:openjdk`, `simple:openrouter`, `simple:opensearch`, `simple:openshift`, `simple:openssl`, `simple:openstack`, `simple:opentelemetry`, `simple:openvpn`, `simple:opsgenie`, `simple:ory`, `simple:packer`, `simple:paddle`, `simple:pagerduty`, `simple:passport`, `simple:paypal`, `simple:perplexity`, `simple:php`, `simple:pinecone`, `simple:planetscale`, `simple:plausibleanalytics`, `simple:playwright`, `simple:pnpm`, `simple:pocketbase`, `simple:podman`, `simple:postgresql`, `simple:posthog`, `simple:postman`, `simple:preact`, `simple:prettier`, `simple:prisma`, `simple:prometheus`, `simple:protonmail`, `simple:pulumi`, `simple:puppeteer`, `simple:python`, `simple:pytorch`, `simple:qdrant`, `simple:qwik`, `simple:rabbitmq`, `simple:radixui`, `simple:railway`, `simple:rancher`, `simple:raspberrypi`, `simple:react`, `simple:reactquery`, `simple:reddit`, `simple:redhat`, `simple:redis`, `simple:redux`, `simple:remix`, `simple:replicate`, `simple:replit`, `simple:resend`, `simple:rollupdotjs`, `simple:ruby`, `simple:rubyonrails`, `simple:rust`, `simple:safari`, `simple:salesforce`, `simple:sanity`, `simple:scala`, `simple:scaleway`, `simple:scikitlearn`, `simple:segment`, `simple:selenium`, `simple:sendgrid`, `simple:sentry`, `simple:shadcnui`, `simple:shopify`, `simple:slack`, `simple:snowflake`, `simple:snyk`, `simple:solid`, `simple:sonarqubeserver`, `simple:splunk`, `simple:springboot`, `simple:sqlite`, `simple:stabilityai`, `simple:stackoverflow`, `simple:storybook`, `simple:strapi`, `simple:stripe`, `simple:supabase`, `simple:surrealdb`, `simple:svelte`, `simple:swagger`, `simple:swift`, `simple:tableau`, `simple:tailscale`, `simple:tailwindcss`, `simple:telegram`, `simple:temporal`, `simple:tensorflow`, `simple:terraform`, `simple:tiktok`, `simple:togetherai`, `simple:traefikproxy`, `simple:trello`, `simple:trpc`, `simple:turborepo`, `simple:turso`, `simple:twilio`, `simple:typescript`, `simple:typesense`, `simple:ubuntu`, `simple:upstash`, `simple:valkey`, `simple:vault`, `simple:vercel`, `simple:vite`, `simple:vitest`, `simple:vmware`, `simple:vuedotjs`, `simple:vultr`, `simple:webassembly`, `simple:webauthn`, `simple:webpack`, `simple:whatsapp`, `simple:wireguard`, `simple:wordpress`, `simple:workos`, `simple:x`, `simple:xero`, `simple:yarn`, `simple:youtube`, `simple:yubico`, `simple:zapier`, `simple:zendesk`, `simple:zerotier`, `simple:zod`, `simple:zoom` ## Where the artwork comes from | Source | License | Used for | |---|---|---| | Lucide | ISC | General icons, and the glyphs behind status icons. | | Simple Icons | CC0 1.0 | Technology and brand logos, and the brand marks of the cloud providers that have one. | | SVG Logos | CC0 1.0 | The full colour marks of a few brands that Simple Icons does not publish (the `simple:` icons drawn in their own colors), such as OpenAI, Slack, Twilio, and Heroku. | | IBM Carbon | Apache-2.0 | The glyph for each kind of cloud service, and the IBM Cloud pictograms. | | Vendor architecture icons (AWS, Microsoft Azure, Google Cloud) | Each vendor's own terms, quoted in [Trademarks and icons](https://docs.plotwork.dev/trademarks-and-icons.md) | The products of that provider. The artwork is drawn as the vendor published it, in its original colors, on a light tile, next to the product name. | A license on the artwork does not license a trademark. Read the Simple Icons disclaimer and each brand's guidelines before using a logo in a public document. ## See also | Doc | Why | |---|---| | [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md#5-icons) | Using icons in a diagram | | [Cloud architecture](https://docs.plotwork.dev/cloud-architecture.md) | Typed groups, provider icons, and how to map a product name to an icon | | [Group types](https://docs.plotwork.dev/cloud-architecture/group-types.md) | The icon each group type draws for each provider, drawn | | [Generic and technology icons](https://docs.plotwork.dev/cloud-icons/generic.md) | Every general icon, technology mark, and status icon, drawn | | [Trademarks and icons](https://docs.plotwork.dev/trademarks-and-icons.md) | Who owns the icons and what each vendor says about using them | | [Language reference](https://docs.plotwork.dev/reference/language.md) | Node types, groups, and their default icons | | [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | The `search_icons` tool |