diagramkit API reference
Make, change, animate and download diagrams from your own code. Every endpoint, parameter, response and error, with examples in curl, JavaScript and Python. Get an API key.
The same reference as plain Markdown, for coding agents: https://learnwithcaret.com/docs/api.md.
Before you start
- The base URL is
https://app.learnwithcaret.com/api/v1. Requests and responses are JSON, except downloads, which are the file itself. - Create a key at https://app.learnwithcaret.com/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 | Pay as you go |
|---|---|---|
A new diagram (POST /diagrams) |
5 | $0.50 |
A change to a diagram (POST /diagrams/{id}/revisions) |
2 | $0.20 |
An animation (animate: true, or POST /diagrams/{id}/animate) |
8 | $0.80 |
| Text edits, downloads, reading and listing | 0 | free |
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.learnwithcaret.com/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.
Quick start
Make a diagram and wait for it in one request, then download it for print:
curl https://app.learnwithcaret.com/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}'
curl "https://app.learnwithcaret.com/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 |
Make it animated in the same request. |
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.
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 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:
{
"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.learnwithcaret.com/api/v1/diagrams/c73141db-…/image.png",
"downloads": {
"png": "https://app.learnwithcaret.com/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. |
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. |
error |
{"type", "message"} or null |
Why a failed job failed. type is generation_failed. |
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.
| 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 |
Vector, for print and layout tools. Paid plans. |
animation.mp4 |
video/mp4 |
quality, size |
A video, up to 90 seconds. Only for an animated diagram. |
animation.gif |
image/gif |
quality, size |
A looping GIF, up to 60 seconds. Only for an animated diagram. |
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. |
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 |
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. |
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.
curl https://app.learnwithcaret.com/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".
# 1. Animate a diagram you already made (it becomes the next version)
curl -X POST https://app.learnwithcaret.com/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.learnwithcaret.com/api/v1/diagrams/DIAGRAM_ID?wait=120" -H "Authorization: Bearer $DIAGRAMKIT_KEY"
# 3. Download it, framed for where it goes
curl "https://app.learnwithcaret.com/api/v1/diagrams/DIAGRAM_ID/animation.mp4?quality=high&size=landscape" -H "Authorization: Bearer $DIAGRAMKIT_KEY" -o explainer.mp4
curl "https://app.learnwithcaret.com/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.
curl https://app.learnwithcaret.com/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. |
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 text
POST /api/v1/diagrams/:id/edits changes wording directly, the way retyping a label on the canvas does: fix a typo, rename a part, change units, shorten a label. It does not use the AI, answers in a few seconds, saves the result as the next version, and does not count as a new AI diagram. For anything beyond wording (layout, parts, colours) ask for a change instead.
curl https://app.learnwithcaret.com/api/v1/diagrams/DIAGRAM_ID/edits \
-H "Authorization: Bearer $DIAGRAMKIT_KEY" \
-H "Content-Type: application/json" \
-d '{"edits": [{"before": "Condensation", "after": "Cloud formation"}, {"before": "Collection", "after": "Collection in oceans and lakes"}]}'
# The job, as GET returns it, with what was applied and what was skipped:
# {..., "version": 3,
# "applied": [{"before": "Condensation", "after": "Cloud formation", "places": 1}],
# "skipped": [{"before": "Collection", "reason": "No text matches this as written. …"}]}
# places is how many times the text appeared and was changed.
| Parameter | In | Values | Default | What it does |
|---|---|---|---|---|
edits |
body | 1 to 30 objects: {"before", "after"} |
required | 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. |
Edits apply to the latest version and are 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. A diagram being made or changed answers 409 conflict.
List and delete
curl "https://app.learnwithcaret.com/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.learnwithcaret.com/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:
// Node 18 or later. Run on your server: the key must stay secret.
import { writeFile } from 'node:fs/promises'
const BASE = 'https://app.learnwithcaret.com/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 3 with requests. Run on your server: the key must stay secret.
import base64, os, requests
BASE = "https://app.learnwithcaret.com/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.learnwithcaret.com/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 agent gets diagramkit's tools: read_diagramkit to find the right kind of figure, preview_visual to draw and check it, and deliver_visual to get the finished figure back as images or one HTML file. Credits are the same as the API's: a diagram is charged once, when it is delivered.
With Claude, through the Messages API's MCP connector:
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.learnwithcaret.com/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:
import os
from openai import OpenAI
response = OpenAI().responses.create(
model="gpt-5.2",
tools=[{
"type": "mcp",
"server_label": "diagramkit",
"server_url": "https://mcp.learnwithcaret.com/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:
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.learnwithcaret.com/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.
{
"error": {
"type": "invalid_request",
"message": "Send {\"prompt\": \"...\"} with optional images, animate (true or false) 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.learnwithcaret.com/settings/api. |
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 not_applied |
None of the text edits could be made: no label matched before, or the text is worked out from data. 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. |
POST |
/api/v1/diagrams/:id/edits |
Change words directly, without the AI. Instant. |
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. |