Saved charts
Durable chart documents with stable image, embed, edit, and management URLs.
A saved chart ties a published document, an optional newer Studio draft, and its metadata to one stable id and a set of URLs. Updating the document keeps the links you already put in an email, a product, or a report, and every earlier publication stays in the chart’s version history.
When to use which
| Need | Use |
|---|---|
| Render one image without keeping identity | POST /chart |
| Publish stable image and embed URLs | POST /api/charts |
| Edit visually and preserve unpublished work | Studio or the web editor |
| Replace a published document in place | PUT /api/charts/{id}/document |
| See earlier publications | GET /api/charts/{id}/versions |
| Go back to an earlier publication | POST /api/charts/{id}/versions/{number}/restore |
| Show a responsive interactive chart | /e/{id} |
| Share a human-facing chart page | /v/{id} |
Creating a saved chart is not charged as a render. A public image or embed request that misses the CDN is.
The chart object
Create, list, read, update, and rename return the same metadata-and-links representation:
type SavedChart = {
id: string;
source: string;
title: string;
createdAt: string;
updatedAt: string;
sizeBytes: number;
publishedAt: string | null;
imageUrl: string;
embedUrl: string;
documentUrl: string;
};source is one of api, app, figma, and mcp; treat an unknown value in a response as a later addition. List items add hasDraft, which says whether unpublished edits exist without reading the document, and the chart’s author: authorName to show, and authorId to filter by with author. A chart whose author has left the workspace, or is unknown, shows Former member.
publishedAt: null means the public image and embed URLs are offline, either because the chart is draft-only or because it was unpublished. createdAt, updatedAt, and non-null publishedAt are ISO-8601 strings.
The object never embeds the document. Fetch the owner-only documentUrl when you need authored state.
Document state
GET /api/charts/{id}/document returns:
type SavedChartDocumentState = {
document: ChartDocument | null;
draft: ChartDocument | null;
publishedAt: string | null;
title: string;
sharedTheme: string | null;
};document is the published revision. draft is a newer unpublished Studio revision when present. A draft-only chart has document: null and no public image or embed.
sharedTheme names the shared theme the published revision is linked to, or is null when it has none or the theme was deleted. A replacement that names no theme keeps this link.
Both are returned as current documents; supported older stored versions are converted on read. A temporary storage failure returns 503 and never substitutes the draft for the publication.
Authentication and ownership
Saved-chart API operations accept either a Bearer API key or the dashboard session, unless noted otherwise. Every metadata and document read is owner-scoped. A missing or unowned id returns 404 so callers cannot test whether another user’s id exists.
Public /c/{id}, /e/{id}, and /v/{id} URLs are opaque capabilities. Anyone holding a published URL can access that output until it is unpublished or deleted and caches expire.
API reference
Create a chart – POST /api/charts
Create and publish a current document:
curl 'https://szum.io/api/charts' \
--fail-with-body \
--max-time 30 \
-X POST \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: report-revenue-2026-q1' \
-d '{
"document": {
"version": "2026-09-26",
"config": {
"version": "2026-09-26",
"type": "column",
"category": { "field": "quarter" },
"values": { "type": "long", "field": "revenue" }
},
"data": [
{ "quarter": "Q1", "revenue": 42 },
{ "quarter": "Q2", "revenue": 58 }
],
"output": { "format": "svg" }
},
"source": "api"
}'Optional title sets the library title, separate from the title drawn in the chart; when omitted, it comes from the document. source defaults to api.
Use one idempotency key per intended chart and reuse it on retries. Deduplication is by key alone, so a reused key with a changed body still returns the original chart; use a fresh key for a different chart. A concurrent retry returns 409 with Retry-After; retry with the same key after that delay. Without a key, every successful request creates a new chart.
Dashboard-session callers may pass draft: true to create a draft-only chart. API-key callers cannot, because drafts belong to the editor.
List charts – GET /api/charts
GET /api/charts?source=api,mcp&sort=updated&q=revenue&limit=100&cursor=...The response is { items, nextCursor }, plus total when search is active or includeTotal=1 is requested.
sourceaccepts one or more ofapi,app,figma, andmcp.sortiscreated,updated, ortitle; the default iscreated.qfilters by a title substring.authoris anauthorIdfrom a list item, orformerfor charts whose author has left the workspace.limitis 1–1,000, defaulting to 100. Larger values are capped; invalid values use the default.cursoris opaque and only valid with the sort it came from.
Listing reads metadata only. It does not load chart documents or consume render allowance.
Get chart metadata – GET /api/charts/{id}
Returns one chart object. It does not read or return document bytes.
Get one document state – GET /api/charts/{id}/document
Returns the published document, optional newer draft, publication timestamp, and library title. The response is private, no-store.
Get documents in batch – GET /api/charts/documents
GET /api/charts/documents?ids=ID_1,ID_2,ID_3Reads up to 100 of your preview documents in one request:
type SavedChartDocuments = {
documents: { id: string; document: ChartDocument }[];
missing: { id: string; reason: "not_found" | "unavailable" }[];
};The preview read prefers a newer draft. not_found covers absent or unowned ids, draft-only charts without a draft, and publication data that is missing or owned by someone else. unavailable covers temporary storage failures and data that cannot be read or converted. A document that parses but fails validation still opens in Studio for repair, but must pass validation before it renders or saves.
Replace a document – PUT /api/charts/{id}/document
Send { document } to publish a replacement while keeping the chart id and all public URLs stable.
Optional title sets the library title, as on create, and an empty title takes it from the document. Without title, a chart renamed in the library keeps its name, and a chart whose title still comes from its document takes the new document’s title. Optional source, one of api, app, figma, and mcp, names the client making this update; it defaults to api.
Szum writes the replacement before switching the published revision to it and updates updatedAt and publishedAt. The previous revision stays in the chart’s version history. If publication fails, the existing revision stays current.
Session callers publish and clear their Studio draft. API-key callers never touch drafts and receive 409 when unpublished Studio changes exist; publish or discard them in Studio first.
To replace only the version you read, send its updatedAt as expectedUpdatedAt beside document. If the chart was published or renamed since, the replacement returns 409 conflict and changes nothing; read the chart again and decide whether to replace it. Without expectedUpdatedAt, the last replacement wins. The Figma plugin sends it on every update, with "source": "figma".
Use a shared theme
In a Team workspace, create and replace accept sharedTheme beside document, the name of one of the workspace’s shared themes, such as "sharedTheme": "Brand colors". The chart takes the theme’s values and stays linked to it, so its image and embed follow later edits to the theme. Names match regardless of case. A name the workspace doesn’t have returns 400 with the names it does have; GET /api/themes lists them with their values and which one is the default.
When a create omits sharedTheme and the document sets neither theme nor themeOverrides, the chart starts in the workspace’s default theme: linked when it’s a shared theme, and with theme set when it’s one of Szum’s. A replacement without sharedTheme keeps the chart’s link, unless the document sets a theme of its own that differs from the linked one; then the chart keeps the document’s theme, unlinked. A document read back from GET /api/charts/{id}/document carries the linked theme’s values, so sending it back keeps the link.
"sharedTheme": null keeps the document’s own theme without a link: a create skips the workspace default, and a replacement ends any link.
Save or discard a Studio draft
PUT /api/charts/{id}/draftDELETE /api/charts/{id}/draft
These endpoints are session-only. They store or clear the owner-scoped unpublished document without publishing, purging CDN, charging render quota, or changing public URLs.
Version history
Every publication of a chart is a version, numbered from 1: creating it, replacing its document from any client, restoring an earlier version, and moving it between workspaces. Publishing a document identical to the current one makes no new version. Drafts are not versions, and unpublishing keeps the current version.
Each version records who published it, when, and how: the client (app, api, figma, or mcp), the API key’s name or the MCP client’s name, and which parts of the chart changed from the version before it. Earlier versions are never public: no image, embed, or share URL serves them, and every history read below is owner-scoped and private, no-store.
History moves with the chart between workspaces and is deleted with it. It counts toward your storage budget and never blocks a publication: when one takes the workspace over budget, the oldest earlier versions lose their content to make room, versions without a name first. A version whose content is gone, because it was pruned or deleted, stays in the list with who published it and when, but it can’t be read or restored.
type SavedChartVersion = {
id: string;
number: number;
kind: "create" | "publish" | "restore" | "move" | "imported";
publishedAt: string | null;
publishedByName: string | null;
client: "app" | "api" | "figma" | "mcp" | null;
apiKeyName: string | null;
mcpClientName: string | null;
restoredFromVersion: { id: string; number: number } | null;
changedSections:
| ("data" | "design" | "size" | "output" | "theme" | "title")[]
| null;
title: string;
label: string | null;
removal: {
at: string;
reason: "pruned" | "deleted";
byName: string | null;
} | null;
sizeBytes: number;
isCurrent: boolean;
};kind: "imported" marks a publication from before version history, whose details are unknown. changedSections is null when the version can’t be compared with the one before it. label is the version’s name. Version objects carry a few more fields, such as user, key, and client ids; treat unknown fields and values as later additions.
List versions – GET /api/charts/{id}/versions
GET /api/charts/{id}/versions?named=1&limit=100&cursor=...Returns { currentVersion, items, nextCursor }, newest first. currentVersion is { id, number } for the version the chart publishes now, or null for a chart never published. named=1 lists only named versions. limit and cursor work as in List charts above.
Read a version – GET /api/charts/{id}/versions/{number}
Returns { version, preparation }. preparation.state is removed when the content is gone, unavailable when it can’t be read now, or retained. A retained version carries its stored document as it was published (stored), a preview of it, and restore: the document a restore would publish under the workspace’s current themes and chart rules, with its validation, any compatibility loss codes, and a fingerprint. A restore with state: "blocked" gives the reason it can’t be published now.
Restore a version – POST /api/charts/{id}/versions/{number}/restore
Publishes the version’s document again as a new version, keeping the chart’s id and URLs; the version it replaces stays restorable. Send a JSON object; every field is optional, so {} restores today’s preparation: { fingerprint?, acceptLosses?, expectedUpdatedAt?, title? }. Send the fingerprint you read to restore exactly that preparation; if it changed, the restore returns 409 and changes nothing. acceptLosses, expectedUpdatedAt, and title work as in Replace a document above. Like a replacement, a restore through the API returns 409 while unpublished Studio changes exist. It returns the chart object with currentVersion.
Copy a version into a new chart – POST /api/charts/{id}/versions/{number}/duplicate
Creates and publishes a new chart from the version, with its own version 1 and no history. The body takes the same optional fingerprint, acceptLosses, and title, and an Idempotency-Key header works as in Create a chart above. Returns 201 with the new chart object.
Name a version – PATCH /api/charts/{id}/versions/{number}
Send { label: string } to name a version, or { label: null } to clear the name. Names are trimmed, and an empty name clears it. Named versions are pruned last. A version whose content was removed can’t be renamed.
Delete versions
DELETE /api/charts/{id}/versions/{number}deletes one earlier version.DELETE /api/charts/{id}/versionswith{ beforeNumber, includeLabelled }deletes every earlier version belowbeforeNumber;includeLabelledmust say whether named ones go too.
Both return { ok: true, affectedCount } and require a workspace admin; a key acts as the person who created it. The current version can’t be deleted: replace it first, or delete the chart. A deleted version loses its content, title, and name, and keeps who published it and when. Clearing a whole workspace’s history is in Studio, under Usage & billing.
Unpublish – POST /api/charts/{id}/unpublish
Unpublishing keeps the chart’s id, metadata, stored publication, and Studio access while making /c/{id}, /e/{id}, and /v/{id} unavailable. Replacing the document with PUT /api/charts/{id}/document publishes it again under the same URLs. Both API-key and session callers can unpublish; the Figma plugin uses it for Unpublish.
Rename – PATCH /api/charts/{id}
Changes only the library title. It does not change config.title, render output, public URLs, publication bytes, or draft state.
Delete – DELETE /api/charts/{id}
Permanently removes the chart, stops serving its URLs, schedules storage cleanup, and requests a CDN purge. HTTP returns 404 when the chart is already gone; the SDK’s delete and MCP delete_chart treat that as success.
To remove every chart, use the delete-all action in account settings rather than deleting charts one by one.
Older configs and loss acceptance
New integrations send { document }. Create and replace also accept one supported 2026-03-20 { config }.
Supported older input is classified as exact, normalized, or lossy before anything is saved. A lossy result lists its loss codes; retry with all of them in acceptLosses. A missing code fails with loss_approval_required, an unknown one with unknown_loss_code. Unsupported input is never written.
Older publications and drafts keep opening without their stored definition being rewritten, and unsupported data is kept rather than replaced. See older config compatibility.
Rendering
Image – GET /c/{id}
The bare URL uses the document’s saved output format. Add .svg or .png to choose one:
<img src="https://szum.io/c/CHART_ID.svg" alt="Quarterly revenue" />Query strings redirect to the canonical URL, so arbitrary parameters cannot split the cache or spend the owner’s allowance.
Interactive embed – GET /e/{id}
<iframe
src="https://szum.io/e/CHART_ID"
title="Quarterly revenue"
loading="lazy"
></iframe>The embed is responsive and adds tooltips and compatible legend filtering to the same saved chart. See Interactive embeds.
Public chart page – GET /v/{id}
The share page adds Szum’s page chrome and OpenGraph metadata, and cannot be indexed or framed. The page itself is not charged; its /c/{id}.png social preview is charged like any image when it misses the CDN.
Caching and updates
Saved images and embed HTML are publicly cached for 24 hours. CDN hits never reach Szum and are not charged. Update, unpublish, and delete request a cache purge; if a purge fails, cached output can stay available until it expires.
Failures are private, no-store, so a temporary storage problem is never cached as the chart’s public state.
Document size
Chart requests accept at most 56 KB. Saved-chart requests, and a saved document both compressed and decoded, are each limited to 64 KB.
These are transport and storage limits, not display limits: Szum never drops rows to fit them. Remove data the chart does not need, or split it into several charts.
Plan and storage
Every signed-in plan can save and publish charts. Free and Creator share the standard storage budget; Pro has a larger one, and Team the largest, shared by its workspace. A publication that would exceed the budget is rejected before it replaces anything.
The budget adds up the compressed size of current publications and their version history; sizeBytes is the current publication’s size, and 0 for a draft-only chart. Only current publications decide whether a new one fits; history then makes room by losing its oldest content. Drafts have the per-document limit but do not count toward the budget. Storage of deleted charts and versions is cleaned up in the background; a failed cleanup never changes which version is current.
On downgrade
Saved charts stay live. For 30 days after a Creator, Pro, or Team plan ends, published charts keep their theme overrides, carry no attribution, and keep that plan’s monthly renders. After that, on Free, the stored document keeps its theme overrides, but delivery drops them and adds attribution; upgrading brings them back, because a downgrade never rewrites the document.
Anything new follows Free from the day the plan ends. Storage over Free’s budget blocks further publishing but deletes nothing. Remove unused charts or upgrade to publish more.
Errors and conflicts
400invalid body, id, query, cursor, semantic document, loss acceptance, title, shared theme name, orexpectedUpdatedAt.401missing or invalid authentication.403current plan cannot publish requested theme overrides, or deleting versions without being a workspace admin.404chart absent or not owned.409a create with the same key still in progress, an id conflict, unpublished Studio changes, a chart that changed sinceexpectedUpdatedAtor since the restorefingerprintyou read, a version whose content was removed, or deleting the current version.413document or account storage limit.429burst limit.500application failure.503temporary publication, database, or Blob read failure.
No route swaps publication and draft state to avoid an error. See Errors for retry guidance.
Rate limits
Metadata and document routes share the ordinary burst limits and are not charged as renders. Public image and embed requests that miss the CDN are charged to the chart owner; CDN hits, draft saves, create, list, reads, rename, unpublish, and delete are not.