# diagramkit API reference

The same reference as plain Markdown, for coding agents: [https://diagramkit.ai/docs/api.md](https://diagramkit.ai/docs/api.md).

## Before you start

- The base URL is `https://app.diagramkit.ai/api/v1`. Requests and responses are JSON, except downloads, which are the file itself.
- Create a key at [https://app.diagramkit.ai/settings/api](https://app.diagramkit.ai/settings/api) and send it on every request, downloads included: `Authorization: Bearer aid_…`. A key is shown once; store it as a secret and revoke it there if it leaks.
- Call the API from your server or a script. Browsers are not allowed to call it from other sites, and a key in a web page is a leaked key.
- Diagrams made with the API appear in your diagrams in the app and use your brand kit and writing style.

## Credits

The API is paid in credits, from the same balance as the app and the MCP connector:

| What | Credits a page | Pay as you go |
|---|---|---|
| A new diagram (`POST /diagrams`) | 5 | $0.50 |
| A change to a diagram (`POST /diagrams/{id}/revisions`), for each page it changes | 2 | $0.20 |
| An animation (`animate: true`, or `POST /diagrams/{id}/animate`) | 8 | $0.80 |
| Edits without the AI (`POST /diagrams/{id}/edits`), downloads, reading and listing | 0 | free |

Most diagrams are one page. A diagram with several pages is priced by its pages: a new three-page diagram is 15 credits, and a change that redraws two of its pages is 4. A request is charged once, when its result is kept; a retry with the same `Idempotency-Key` is not charged again. Your plan's credits are used first, then bought credits. Buy credits or turn on automatic top-up at [https://app.diagramkit.ai/settings/api](https://app.diagramkit.ai/settings/api), where you also see your balance and this month's use. When the balance cannot pay for a request, it is refused with `402 no_credits` before any work starts, and nothing is charged.

A set of pages (slides, a carousel, "one page per step") has as many pages as the prompt asks for. It is planned first, and its whole cost is checked before any page is drawn: a set the balance cannot pay for fails with `error.type` `no_credits`, a message saying how many pages were planned, the credits they need and the balance, and nothing drawn or charged. The API never asks before drawing, so a set of more than 30 pages needs your yes: send `max_pages` with the most pages you accept. Without it, a longer set fails with `error.type` `too_many_pages` and a message naming its page count and cost; send the request again with `max_pages`. A job's time limit grows with its pages.

## Quick start

Make a diagram and wait for it in one request, then download it for print:

```bash
curl https://app.diagramkit.ai/api/v1/diagrams \
  -H "Authorization: Bearer $DIAGRAMKIT_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: book-7-figure-3.2" \
  -d '{"prompt": "The water cycle for a grade 7 science textbook: evaporation, condensation, precipitation and collection, with the sun driving it", "wait": 180}'
```

```bash
curl "https://app.diagramkit.ai/api/v1/diagrams/DIAGRAM_ID/image.png?quality=print&width_in=3.25" \
  -H "Authorization: Bearer $DIAGRAMKIT_KEY" -o figure-3-2.png
```

## Make a diagram

`POST /api/v1/diagrams`. Making one takes about a minute, sometimes two.

| Parameter | In | Values | Default | What it does |
|---|---|---|---|---|
| `prompt` | body | text, 1 to 4,000 characters | required | What to draw. Paste notes, data or a passage from your text into it. |
| `images` | body | up to 4 objects: `{"name", "mime", "data_base64"}` | `[]` | Pictures to work from: a sketch, a photo of a whiteboard, an existing figure, a logo. `mime` is `image/png`, `image/jpeg`, `image/webp` or `image/gif`; `data_base64` is the file's bytes in standard base64, without a `data:` prefix. Up to 6 MB and 25 megapixels each. |
| `animate` | body | `true`, `false` | `false`, or `true` for the `ad` and `explainer_video` presets | Make it animated in the same request. |
| `preset` | body | `docs`, `blog`, `social`, `carousel`, `ad`, `data_story`, `explainer_video`, or null | none | Where the diagram will be used. See [Presets](#presets). |
| `max_pages` | body | 1 to 100 | 30 | The most pages a set may draw. A set with more fails with `too_many_pages` before anything is drawn or charged. Also on revisions and `animate`. |
| `wait` | body | whole seconds, 0 to 300 | `0` | Hold the request open until the diagram is done or this time passes. With 0 you get `202` and the job at once. |
| `Idempotency-Key` | header | any text, up to 200 characters | none | Name each figure (`book-7-figure-3.2`). A request with a key this account has used before returns that job, whatever its body, instead of making another. Keys do not expire. |

Write the prompt the way you would brief an illustrator: what the figure must show, who it is for, the labels and numbers it needs, and the size it will be seen at ("for a phone screen", "a full textbook page"). The API never stops to ask questions: when something is ambiguous it makes a reasonable choice and says so in `description`.

## Presets

A preset says where the diagram will be used, not what kind of diagram it is. It changes how the agent composes it (shape, words, emphasis, pages) and which download comes first; the prompt's own words override it. It costs nothing extra: credits are per page as always. The diagram keeps its preset, so every change to it is made for the same destination, until a change names another (`preset` on revisions, or null for none). Leave it out for a general diagram, exactly as before.

| Preset | Made for | Animates | Pages | First download |
|---|---|---|---|---|
| `docs` | Documentation: accurate and neutral, no headline or call to action | no | one | `image.png` |
| `blog` | A figure in an article: one point, landscape about 2:1 | no | one | `image.png?quality=high` |
| `social` | A LinkedIn or X post: one idea readable at phone width, 4:5 or square, no call to action | no | one | `image.png?quality=high`; recordings `size=portrait` |
| `carousel` | A LinkedIn or Instagram carousel: a cover with a hook and a swipe cue, one idea per numbered page (`n / N`), an end card; 4:5, 6 to 12 pages unless the prompt asks for more. The prompt's own sentences stay on the pages word for word, and a page gets a figure only where it shows structure | no | many | `image.pdf?pages=all` |
| `ad` | Paid social: a short line, the product largest, brand bottom left and call to action bottom right, a 1:1 master | yes | one | `animation.mp4?size=square` |
| `data_story` | One chart from real data whose title is the finding, with a small source line | no | one | `image.png?quality=high` |
| `explainer_video` | An animated explainer composed for 16:9 | yes | one | `animation.mp4?size=landscape` |

A job with a preset carries `recommended_download`, the download its preset offers first, and `animation.*` without `size` uses the preset's frame.

```bash
curl https://app.diagramkit.ai/api/v1/diagrams \
  -H "Authorization: Bearer $DIAGRAMKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "How SSH sets up an encrypted session", "preset": "social", "wait": 180}'
```

## Wait for it

Send `wait` to hold the request open, up to 300 seconds. If the diagram is not done by then you get `202` with the job; call `GET /api/v1/diagrams/:id?wait=60` until `status` is `succeeded` or `failed`. Set your HTTP client's timeout above `wait` (wait plus 30 seconds). A job that runs longer than 15 minutes fails; a set of pages has more time, about 6 minutes for every 4 pages after the first 4. A failed request leaves the diagram as it was (a new one has no version); send it again with a new `Idempotency-Key`.

| Parameter | On | Values | Default | What it does |
|---|---|---|---|---|
| `wait` | `GET /api/v1/diagrams/:id` | whole seconds, 0 to 300 | `0` | Answer when the job finishes or after this long, whichever is first. |
| `limit` | `GET /api/v1/diagrams` | 1 to 100 | `20` | How many recent diagrams to list. |

A finished job:

```json
{
  "id": "c73141db-d6c0-4e4b-94ee-0aa757886498",
  "status": "succeeded",
  "title": "How the water cycle works",
  "description": "The sun drives evaporation from the ocean; vapour condenses into clouds, falls as rain and collects in rivers. It assumes a simplified cycle without groundwater.",
  "version": 1,
  "animated": false,
  "progress": null,
  "image_url": "https://app.diagramkit.ai/api/v1/diagrams/c73141db-…/image.png",
  "downloads": {
    "png": "https://app.diagramkit.ai/api/v1/diagrams/c73141db-…/image.png",
    "jpg": "…/image.jpg", "svg": "…/image.svg", "pdf": "…/image.pdf"
  },
  "error": null,
  "created_at": "2026-10-15T14:49:58.000Z"
}
```

| Field | Type | Meaning |
|---|---|---|
| `id` | string (UUID) | The diagram. Use it in every other call. |
| `status` | `queued`, `running`, `succeeded`, `failed` | The latest request on this diagram: the first one or the latest change. |
| `title` | string or null | The diagram's title, once it has one. |
| `description` | string or null | What the diagram shows and any assumptions it made. Read it: it says when something was ambiguous. |
| `version` | number | How many versions it has. 0 until the first is made; each change adds one. |
| `animated` | `true`, `false` | Whether the latest version is animated, so `animation.mp4` and `.gif` work. |
| `preset` | string or null | The diagram's [preset](#presets), or null. |
| `progress` | string or null | While queued or running, what is happening ("Drawing the diagram"). |
| `image_url` | string or null | The PNG of the latest version, once the job has succeeded. |
| `downloads` | object or null | Links to every format of the latest version: `png`, `jpg`, `svg`, `pdf`, and `mp4` and `gif` when animated. null until there is a version. Send your key with them. |
| `recommended_download` | `{"format", "url", "options"}` | With a preset, once there is a version: the download it offers first (`options` are the query parameters in `url`). Left out without a preset. |
| `error` | `{"type", "message"}` or null | Why a failed job failed. `type` is `no_credits` (the balance cannot pay for the planned pages), `too_many_pages` (a set longer than `max_pages`, or than 100 pages), or `generation_failed`. The first two are refused before anything is drawn or charged; `message` says how many pages were planned and what they cost. |
| `created_at` | ISO 8601 time | When the diagram was first requested. |

## Download it

`GET` any of these after `/api/v1/diagrams/:id/`, with your key. The response is the file, with its `Content-Type`. Files are made on request: a picture takes a few seconds, a recording 20 to 60; the same request again is answered from cache. Downloads are always of the latest version. SVG and PDF are on paid plans: on the free plan they answer `403` with `error.type` `plan_required`, which a retry will not change; PNG and JPEG work on every plan.

| Path | Content-Type | Parameters | What you get |
|---|---|---|---|
| `image.png` | `image/png` | `device`, `quality`, `width_in` | A sharp picture for slides, docs and the web. |
| `image.jpg` | `image/jpeg` | `device`, `quality`, `width_in` | A smaller picture for email and social posts. |
| `image.svg` | `image/svg+xml` | `device` | Vector, every label editable text, for Figma, Illustrator and the web. Paid plans. |
| `image.pdf` | `application/pdf` | `device`, `pages` | Vector, for print and layout tools; `pages=all` makes one PDF of every page of a multi-page diagram. Paid plans. |
| `animation.mp4` | `video/mp4` | `quality`, `size`, `page` | A video, up to 90 seconds. Only for an animated diagram. A multi-page diagram plays every page in order, with a short fade between pages, unless `page` picks one; a long one holds each still page for less so it fits (at least 1.5 seconds), and one too long even so is refused with `422 too_long`. |
| `animation.gif` | `image/gif` | `quality`, `size`, `page` | A looping GIF, up to 60 seconds. Only for an animated diagram. Pages as for `animation.mp4`. |

Parameters go in the query string, for example `image.png?quality=print&width_in=3.25&device=phone`:

| Parameter | For | Values | Default | What it does |
|---|---|---|---|---|
| `device` | `image.*` | `desktop`, `phone` | `desktop` | Which layout: desktop is drawn 900 px wide, phone 390 px wide. Every diagram has both. |
| `quality` | `image.png`, `image.jpg` | `web`, `high`, `print` | `web` | `web` is 1× (900 px wide) and instant; `high` is 2× (1800 px); `print` is 300 dpi at `width_in` inches, with the dpi written into the file. |
| `page` | `image.*` | 1 to 100 | `1` | Which page of a multi-page diagram. |
| `pages` | `image.pdf` | `all` | none | Every page as one PDF, a page each. |
| `width_in` | `image.png`, `image.jpg` with `quality=print` | a number from 1 to 20 (inches, decimals allowed) | `6.5` | The printed width. 6.5 fills a textbook page (1950 px); 3.25 fits one column (975 px). Ignored for other qualities. |
| `quality` | `animation.*` | `low`, `medium`, `high` | `medium` | MP4: low 675 px at up to 24 fps, medium 900 px at 30 fps, high 1800 px at up to 60 fps. GIF: low 675 px at 15 fps, medium 900 px at 20 fps, high 1350 px at 30 fps. Widths are for `size=chat`; framed sizes scale the same way (low is 0.75×, high up to 2×). Higher is larger and slower to record. |
| `size` | `animation.*` | `chat`, `square`, `portrait`, `story`, `landscape` | the preset's frame, or `chat` | The frame: chat is the diagram's own shape; square 1080×1080 (1:1); portrait 1080×1350 (4:5); story 1080×1920 (9:16); landscape 1920×1080 (16:9). The diagram is fitted as large as the frame allows. |
| `page` | `animation.*` | 1 to 100 | none | One page of a multi-page diagram. Left out, every page plays in order in one video: an animated page plays its animation, a still page holds for about 3 seconds, or less in a long set so it fits the format's length (never under 1.5 seconds). About 27 still pages fit a GIF and 41 an MP4; for more, download `image.pdf?pages=all` or one page at a time. |

## Animate a diagram

An animation is the same diagram building itself up: parts arrive in order, arrows draw from start to end, labels appear with what they describe, and it ends on the finished, readable picture. diagramkit plans the motion from what the diagram explains; most run 5 to 10 seconds. There are two ways to get one.

**Animated from the start.** Add `"animate": true` when you make it. One request, one to two minutes.

```bash
curl https://app.diagramkit.ai/api/v1/diagrams \
  -H "Authorization: Bearer $DIAGRAMKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "How a heat pump moves heat from outside air into a house", "animate": true, "wait": 300}'
```

**Animate one you already have.** `POST /api/v1/diagrams/:id/animate` keeps the drawing and adds motion as the next version. Use `"prompt"` to steer it, for example "one step at a time", "slower", "highlight the cache miss path last" or "loop the data flow at the end".

```bash
# 1. Animate a diagram you already made (it becomes the next version)
curl -X POST https://app.diagramkit.ai/api/v1/diagrams/DIAGRAM_ID/animate \
  -H "Authorization: Bearer $DIAGRAMKIT_KEY" -H "Content-Type: application/json" \
  -d '{"prompt": "Build it up one step at a time, then loop the refrigerant around the circuit", "wait": 300}'

# 2. If it is not done yet, wait for it: "animated": true means it is ready
curl "https://app.diagramkit.ai/api/v1/diagrams/DIAGRAM_ID?wait=120" -H "Authorization: Bearer $DIAGRAMKIT_KEY"

# 3. Download it, framed for where it goes
curl "https://app.diagramkit.ai/api/v1/diagrams/DIAGRAM_ID/animation.mp4?quality=high&size=landscape" -H "Authorization: Bearer $DIAGRAMKIT_KEY" -o explainer.mp4
curl "https://app.diagramkit.ai/api/v1/diagrams/DIAGRAM_ID/animation.gif?quality=medium&size=portrait" -H "Authorization: Bearer $DIAGRAMKIT_KEY" -o post.gif
```

A job is ready to record when it shows `"animated": true`; its `downloads` then include `mp4` and `gif`. The `image.*` downloads keep giving the finished still.

| Where it goes | Download |
|---|---|
| Slides, a course video, YouTube | `animation.mp4?size=landscape&quality=high` |
| YouTube Shorts, Reels, TikTok | `animation.mp4?size=story` |
| LinkedIn and Instagram posts | `animation.gif?size=portrait` or `animation.mp4?size=portrait` |
| X and other square feeds | `animation.gif?size=square` |
| Docs, READMEs, Slack, email | `animation.gif?quality=low` (small, and loops) |

## Change a diagram

`POST /api/v1/diagrams/:id/revisions`. The change keeps everything you did not mention and becomes the next version; downloads give it from then on. One change at a time: a second one while the first runs gets `409 conflict`.

```bash
curl https://app.diagramkit.ai/api/v1/diagrams/DIAGRAM_ID/revisions \
  -H "Authorization: Bearer $DIAGRAMKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Label the arrows with the time each step takes", "wait": 180}'
```

| Parameter | In | Values | Default | What it does |
|---|---|---|---|---|
| `prompt` | revisions body | text, 1 to 4,000 characters | required | What to change: "label the arrows with the time each step takes", "use metric units", "make it fit a narrow column". |
| `images` | revisions body | as for making one | `[]` | Pictures to work from for the change. |
| `preset` | revisions body | a [preset](#presets), or null for none | the diagram's own | Make this change, and later ones, for another destination. |
| `prompt` | animate body | text, up to 1,000 characters | none | How it should move, if you have a preference. |
| `wait` | both bodies | whole seconds, 0 to 300 | `0` | Hold the request open until the new version is done. |
| `Idempotency-Key` | header | any text, up to 200 characters | none | As for making one: a key used before returns that job. |

## Edit without the AI

`POST /api/v1/diagrams/:id/edits` makes exact changes directly, as the canvas editor makes them: retype a label, recolour a box, a line or its text, change a font or size, hide or show a part, move a part, put it beside another, swap two, line parts up or space them evenly. It does not use the AI, answers in a few seconds, costs no credits, saves the result as the next version, and does not count as a new AI diagram. For anything else (new content, a different layout) ask for a change instead.

Wording is matched by the text itself. Everything else names parts by their ids, from the parts list:

```bash
curl "https://app.diagramkit.ai/api/v1/diagrams/DIAGRAM_ID/parts?page=1" -H "Authorization: Bearer $DIAGRAMKIT_KEY"

# {"diagram_id": "…", "version": 3, "page": 1, "layouts": {"wide": 900, "compact": 390}, "truncated": false, "inner": 48,
#  "parts": [
#   {"id": "node:api", "kind": "node", "summary": "node \"API\"; to \"Cache\", \"Database\"; 2 in; movable; 6 inner parts",
#    "text": "API", "movable": true, "inner": 6, "wide": [361, 148, 79, 105], "compact": [176, 163, 63, 84]},
#   {"id": "node:db", "kind": "node", "summary": "node \"Database\"; 2 in; movable; 4 inner parts", …},
#   {"id": "link:api->db", "kind": "link", "summary": "link \"API\" -> \"Database\"; 2 inner parts",
#    "link": {"from": "api", "to": "db"}, "wide": [431, 187, 104, 119], "compact": [53, 204, 131, 247]}, …]}
```

A part's `id` comes from what it is, so it stays the same through edits, and through a change from the AI that keeps the part:

| Id | What it names |
|---|---|
| `node:<id>` | A part diagramkit placed, by the id the diagram gives it (`node:api`). |
| `link:<from>-><to>` | A connection, with its line and arrowheads (`link:api->db`). |
| `note:<words>`, `text:<words>`, `box:<words>` | A note, free-standing words (a title, a label, a caption) or a box, by its words (`text:monthly-revenue`). |
| `cell:<id>`, `cell:<row>/<col>` | A placed cell by the id of the part in it; a table cell by row and column (`cell:0/2` is the second header). |
| `mark:<series>/<category>` | A chart's bar or point (`mark:revenue/2024`), a whole line by its series, a pie slice by its id. |
| `<owner>/<kind>` | Something inside one of those: a node's words (`node:api/text`), an icon's shapes (`node:api/shape#2`), a link's `line` and `head`. |

Two parts with the same name are told apart in the order the diagram draws them: `text:total`, `text:total#2`. A part's position (`0/2/1`, its `path`) is accepted too.

By default the list has what a person points at: nodes (with their words), links, notes, cells, chart marks, titles and labels. Each has its `kind` (`node`, `cell` or `note` for parts diagramkit placed; `link`; `mark`; `text`, `line`, `shape` or `box`), a one-line `summary`, its first 40 characters of `text`, `inner` (how many parts sit inside it), and its box `[left, top, width, height]` in pixels in the `wide` layout (desktop, 900 px wide, as `image.png` draws it) and, where it shows, the `compact` layout (phone, 390 px). `movable` marks the parts that move; `hidden` marks a hidden part; `offset` is a moved part's current offset per layout. `detail=true` lists what sits inside them too, each with its `tag`, its `path` and `moves_with`, the placed part it moves with. A large diagram lists its first 400 parts and says `truncated`.

```bash
curl https://app.diagramkit.ai/api/v1/diagrams/DIAGRAM_ID/edits \
  -H "Authorization: Bearer $DIAGRAMKIT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": [{"before": "Cache", "after": "Redis cache"}],
       "place": [{"part": "node:db", "relative_to": "node:cache", "side": "below"}],
       "restyle": [{"part": "link:api->db", "style": {"stroke": "#d92d20"}}]}'

# The job, as GET returns it, with what was applied, what was skipped and where each moved part is now:
# {..., "version": 4,
#  "applied": [{"before": "Cache", "after": "Redis cache", "places": 1}],
#  "skipped": [],
#  "moved": [{"part": "node:db", "wide": [505, 260, 86, 105]}]}
# places is how many times the text appeared and was changed.
```

| Parameter | In | Values | Default | What it does |
|---|---|---|---|---|
| `text` | body | up to 30 objects: `{"before", "after"}` | `[]` | Each replaces one label or line of text. `before` is the text exactly as it appears in the diagram (up to 600 characters); `after` is what it should say. Also accepted as `edits`. |
| `restyle` | body | up to 100 objects: `{"part", "style"}` | `[]` | `style` sets any of `fill`, `stroke`, `color`, `background-color`, `font-family`, `font-size` (4 to 400 px), `font-weight` (`normal`, `bold`, 100 to 900) and `visibility`. `fill` colours a shape, or a box's background; `color` its text. On a link, `stroke` (or `color`) colours its line and arrowheads; on a chart mark, `fill` its shape; on a node, `color` its words. Values are plain CSS: colours (`#0b5fff`, `rgb()`, `hsl()`, `var(--…)`), lengths and font names; anything else is refused. An empty value takes back an earlier change. A fill that leaves text on it hard to read re-colours that text from the diagram's own colours, as the canvas does. |
| `hide` | body | up to 100 part ids | `[]` | Hide parts. |
| `show` | body | up to 100 part ids | `[]` | Show parts hidden earlier with `hide`. |
| `nudge` | body | up to 100 objects: `{"part", "dx", "dy", "layout"}` | `[]` | Shift a movable part (or the one a part sits in) `dx`, `dy` pixels from where it is now; right and down are positive. |
| `place` | body | up to 100 objects: `{"part", "relative_to", "side", "gap", "layout"}` | `[]` | Put a movable part `left`, `right`, `above` or `below` another part, `gap` pixels away (default 40), centred on it across that side. The other part need not move. |
| `swap` | body | up to 50 objects: `{"a", "b", "layout"}` | `[]` | Exchange two movable parts' positions, centre for centre. |
| `move` | body | up to 100 objects: `{"part", "layout", "dx", "dy"}` | `[]` | Set a movable part's offset from where diagramkit placed it, -4000 to 4000 pixels. `0, 0` puts it back. |
| `align` | body | `{"parts", "to", "layout"}` | none | Line up two or more placed parts with each other: `to` is `left`, `center` or `right` (across) or `top`, `middle` or `bottom` (down). |
| `distribute` | body | `{"parts", "axis", "layout"}` | none | Space three or more placed parts evenly between the first and the last: `axis` is `horizontal` or `vertical`. |
| `page` | body | 1 to 100 | `1` | Which page of a multi-page diagram; part ids are per page. |
| `styles`, `moves` | body | the canvas editor's own form | `[]` | What the web app's canvas sends: styles by element position, moves by part key. Prefer the fields above. |

`layout` is `wide` (desktop, the default), `compact` (phone) or `both` (each layout the part has). Lines follow a part that moves. Send at least one edit. Moves apply in this order: `move`, `nudge`, `place`, `swap`, `align`, `distribute`, each measuring the parts where the earlier ones left them, and the result's `moved` gives each moved part's new box per layout, so there is no need to list the parts again. A layout a diagram does not have (one drawn the same on desktop and phone has only `wide`) is refused with a message saying which to use.

Edits apply to the latest version, and wording is matched exactly, case included. Text the diagram works out from data, such as a chart's axis values or a computed total, cannot be retyped: change the data with a revision. An unknown part id (the message lists ids of that kind), a part that cannot move, a part placed beside itself, or a change that would break the diagram answers `422 not_applied` naming the part and the reason, and the diagram is left as it was. A diagram being made or changed answers `409 conflict`.

## List and delete

```bash
curl "https://app.diagramkit.ai/api/v1/diagrams?limit=5" -H "Authorization: Bearer $DIAGRAMKIT_KEY"
# {"diagrams": [{"id", "title", "status", "versions", "origin", "created_at", "updated_at"}, …]}

curl -X DELETE https://app.diagramkit.ai/api/v1/diagrams/DIAGRAM_ID -H "Authorization: Bearer $DIAGRAMKIT_KEY"
# {"deleted": true}
```

Deleting removes the diagram, every version and every file made from it. It cannot be undone.

## Full examples

Make a diagram, wait for it, and save it, in JavaScript:

```js
// Node 18 or later. Run on your server: the key must stay secret.
import { writeFile } from 'node:fs/promises'

const BASE = 'https://app.diagramkit.ai/api/v1'
const headers = { Authorization: 'Bearer ' + process.env.DIAGRAMKIT_KEY, 'Content-Type': 'application/json' }

async function call(path, init = {}) {
  const res = await fetch(BASE + path, { ...init, headers: { ...headers, ...init.headers }, signal: AbortSignal.timeout(330_000) })
  if (!res.ok) throw new Error((await res.json()).error.message)
  return res
}

// 1. Make it, waiting up to three minutes.
let job = await (await call('/diagrams', {
  method: 'POST',
  headers: { 'Idempotency-Key': 'book-7-figure-3.2' },
  body: JSON.stringify({ prompt: 'The water cycle for a grade 7 science textbook', wait: 180 }),
})).json()

// 2. Not done yet? Keep waiting, 60 seconds at a time.
while (job.status === 'queued' || job.status === 'running') {
  job = await (await call('/diagrams/' + job.id + '?wait=60')).json()
}
if (job.status === 'failed') throw new Error(job.error.message)
console.log(job.title, '-', job.description)

// 3. Download it for print, one column wide.
const png = await call('/diagrams/' + job.id + '/image.png?quality=print&width_in=3.25')
await writeFile('figure-3-2.png', Buffer.from(await png.arrayBuffer()))
```

Work from a sketch and save an editable SVG, in Python:

```python
# Python 3 with requests. Run on your server: the key must stay secret.
import base64, os, requests

BASE = "https://app.diagramkit.ai/api/v1"
HEADERS = {"Authorization": "Bearer " + os.environ["DIAGRAMKIT_KEY"]}

# Work from a sketch: images are base64 without a data: prefix.
with open("sketch.png", "rb") as f:
    sketch = base64.b64encode(f.read()).decode()

job = requests.post(f"{BASE}/diagrams", headers={**HEADERS, "Idempotency-Key": "ch4-fig2"}, timeout=330, json={
    "prompt": "Redraw this sketch of the heart's four chambers, labeled, for a nursing course",
    "images": [{"name": "sketch.png", "mime": "image/png", "data_base64": sketch}],
    "wait": 180,
}).json()

while job["status"] in ("queued", "running"):
    job = requests.get(f"{BASE}/diagrams/{job['id']}", headers=HEADERS, params={"wait": 60}, timeout=90).json()
if job["status"] == "failed":
    raise RuntimeError(job["error"]["message"])

svg = requests.get(f"{BASE}/diagrams/{job['id']}/image.svg", headers=HEADERS, timeout=120)
svg.raise_for_status()
open("ch4-fig2.svg", "wb").write(svg.content)
```

## Use the MCP from your own agent

An agent you build can use diagramkit as a tool through its MCP server instead of calling these endpoints. Connect it to `https://mcp.diagramkit.ai/mcp` and send the same API key as the bearer token: `Authorization: Bearer aid_…`. No sign-in window opens; the key decides whose diagrams and credits it uses, and revoking the key cuts the agent off within a minute. (People connecting their own Claude or ChatGPT use the connector's sign-in instead.)

The MCP is another way in to the same diagramkit agent as these endpoints, with the same features: `create_diagram` sends a request, with the same `animate` and `preset`, and returns the finished image and ids (one to three minutes), `revise_diagram` and `animate_diagram` ask for changes, `edit_diagram` makes the edits above without the AI (with the part ids `get_diagram` lists when you pass `parts: true`, and `detail: true` for what sits inside them), `get_diagram`, `list_diagrams` and `delete_diagram` manage diagrams, and `download_diagram` returns PNG, JPG, SVG, PDF, MP4 or GIF. Where the host displays MCP Apps, `render_visual` shows the diagram in the chat. Credits are the same as the API's.

With Claude, through the Messages API's MCP connector:

```bash
curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: mcp-client-2025-11-20" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-5-5",
    "max_tokens": 8000,
    "messages": [{"role": "user", "content": "Draw how a CDN serves a cached image, and give me the PNG."}],
    "mcp_servers": [{"type": "url", "url": "https://mcp.diagramkit.ai/mcp", "name": "diagramkit", "authorization_token": "'"$DIAGRAMKIT_KEY"'"}],
    "tools": [{"type": "mcp_toolset", "mcp_server_name": "diagramkit"}]
  }'
```

With OpenAI, through the Responses API's remote MCP tool:

```python
import os
from openai import OpenAI

response = OpenAI().responses.create(
    model="gpt-5.2",
    tools=[{
        "type": "mcp",
        "server_label": "diagramkit",
        "server_url": "https://mcp.diagramkit.ai/mcp",
        "authorization": os.environ["DIAGRAMKIT_KEY"],
        "require_approval": "never",
    }],
    input="Draw how a CDN serves a cached image, and give me the PNG.",
)
print(response.output_text)
```

With any MCP client, such as the TypeScript SDK in your own agent loop:

```js
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'

const client = new Client({ name: 'my-agent', version: '1.0.0' })
await client.connect(new StreamableHTTPClientTransport(new URL('https://mcp.diagramkit.ai/mcp'), {
  requestInit: { headers: { Authorization: 'Bearer ' + process.env.DIAGRAMKIT_KEY } },
}))
const { tools } = await client.listTools() // hand these to your model
```

A key that is not valid, or was revoked, is refused with `401` and the message to make a new one; the key is never sent to the model, only to diagramkit.

## Responses and errors

Every error has the same shape; `issues` appears on `invalid_request`.

```json
{
  "error": {
    "type": "invalid_request",
    "message": "Send {\"prompt\": \"...\"} with optional images, animate (true or false), preset (docs, blog, social, carousel, ad, data_story, explainer_video) and wait in seconds (0-300).",
    "issues": ["wait: Too big: expected number to be <=300"]
  }
}
```

| Status | Meaning |
|---|---|
| `200` | Done: the body is the finished job, or the file you asked for. |
| `202` | Accepted and still being made: the body is the job, with its id. Wait for it. |
| `400 invalid_request` | A parameter is missing or out of range. `message` says which; `issues` lists each problem. |
| `401 unauthorized` | No key, a malformed one, or a revoked one. |
| `402 no_credits` | The balance cannot pay for this request. `message` says how many credits it needs and when plan credits return. Add credits, or turn on automatic top-up, at https://app.diagramkit.ai/settings/api. |
| `403 plan_required` | The account's plan does not include this download: SVG and PDF are on paid plans. Do not retry; upgrade, or download PNG or JPEG. |
| `404 not_found` | No diagram with that id on this account, or it has no picture yet. |
| `409 conflict` | A change is already being made to this diagram. Wait for it, then send the next. |
| `409 not_animated` | You asked for an animation from a diagram that has none. Animate it first. |
| `422 too_long` | A video of every page would be too long for the format even at 1.5 seconds a still page. `message` says how many pages fit (`pages_fit`) and offers an MP4 or the all-pages PDF. |
| `422 not_applied` | An edit could not be made: no label matched `before`, an unknown part id, a part that cannot move, nothing changed, or the change would break the diagram (it is left as it was). `message` says why. |
| `429 rate_limited` | 50 requests are already in progress on this account. Wait for some to finish. |
| `503 unavailable` | The file could not be made right now. Retry shortly. |

## All endpoints

| Method | Path | What it does |
|---|---|---|
| `POST` | `/api/v1/diagrams` | Make a diagram. Add `"animate": true` for an animated one. |
| `GET` | `/api/v1/diagrams/:id` | The job: its status and download links. Add `?wait=` to wait for it. |
| `POST` | `/api/v1/diagrams/:id/revisions` | Ask for a change. It becomes the next version. |
| `POST` | `/api/v1/diagrams/:id/animate` | Animate a diagram you already made. |
| `GET` | `/api/v1/diagrams/:id/parts` | The editable parts of the latest version, with their ids, summaries and boxes (`detail=true` for every part). |
| `POST` | `/api/v1/diagrams/:id/edits` | Edit directly, without the AI: wording, colours, fonts, hide and show, nudge, place, swap, move, align, distribute. Instant, free. |
| `GET` | `/api/v1/diagrams/:id/image.png` | The picture. Also `image.jpg`, `image.svg` and `image.pdf`. |
| `GET` | `/api/v1/diagrams/:id/animation.mp4` | The animation as a video. Also `animation.gif`. |
| `GET` | `/api/v1/diagrams` | Your recent diagrams, newest first. |
| `DELETE` | `/api/v1/diagrams/:id` | Delete a diagram and every version of it. |
