diagramkit

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.