Create, list, and delete hosted embeds — charts and docs (HTML or markdown pages) — programmatically. Built for scripts and agents: send data or content, get back a live URL and an iframe snippet. API access is free during the beta; after launch it will require a Pro plan.
https://jqjkhlrgstlixasyqrbk.supabase.co/functions/v1/api
The CLI and MCP server honor an EMBD_API_URL override. For the curl examples below, export the base URL and your key once:
export EMBD_API_URL="https://jqjkhlrgstlixasyqrbk.supabase.co/functions/v1/api" export EMBD_API_KEY="embd_sk_..."
Every request — except POST /embeds/:ref/unlock, which exists for viewers, not owners — needs an API key, created on your account page under API keys, sent as Authorization: Bearer embd_sk_.... Keys look like embd_sk_ + 43 url-safe characters. Only a SHA-256 hash is stored server-side; a lost key cannot be recovered — revoke it and mint a new one. Revoked keys are rejected immediately.
All bodies are JSON. Fields:
data (required) — CSV text, or an array of flat row objects.kind — line (default), area, bar, stacked-bar, stacked-area, bar-horizontal, scatter, pie, donut, heatmap, radar.title — chart title.x — X-axis column. Default: first non-numeric column, else first column.y — measure column(s), string or array. Default: all numeric columns except x (capped at 8).split — column whose distinct values split the data into series.theme — theme id, e.g. aurora-dark (default), simple-light, vibrant-dark, neon-light.publish — default true. false saves a draft (no public page).slug — vanity URL (/c/<slug>). Pro only. 3–40 lowercase letters/numbers with single hyphens.Datasets are stored inline and capped at 100,000 encoded characters.
curl -X POST "$EMBD_API_URL/charts" \
-H "Authorization: Bearer $EMBD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": "month,revenue,cost\nJan,1200,800\nFeb,1900,950\nMar,3000,1100",
"kind": "bar",
"title": "Q1 revenue vs cost"
}'201 Created:
{
"id": "k3v9x2ab",
"url": "https://app.embd.sh/c/k3v9x2ab",
"embedUrl": "https://app.embd.sh/embed/k3v9x2ab",
"iframe": "<iframe src=\"https://app.embd.sh/embed/k3v9x2ab\" ...></iframe>",
"title": "Q1 revenue vs cost",
"kind": "bar",
"status": "published"
}Row objects work the same way:
curl -X POST "$EMBD_API_URL/charts" \
-H "Authorization: Bearer $EMBD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"data": [{"day": "Mon", "visits": 120}, {"day": "Tue", "visits": 340}], "kind": "line"}'Host a self-contained HTML page or a markdown document. HTML renders at /html/:ref (markdown at /markdown/:ref; legacy /c/:ref links redirect by kind). HTML source is stored verbatim and rendered inside a fully inert sandboxed iframe (sandbox="" — no scripts, no allow-same-origin): markup displays, nothing executes. A script-enabled tier on a separate content origin is planned. Fields:
content (required) — the HTML or markdown source. Up to 100,000 characters.format (required) — html or markdown.title — optional. Derived when omitted: first markdown heading, or the HTML <title>; otherwise untitled.publish — default true. false saves a draft (no public page).slug — vanity URL (/c/<slug>). Pro only. Same rules as charts.curl -X POST "$EMBD_API_URL/docs" \
-H "Authorization: Bearer $EMBD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"format": "markdown",
"content": "# Release notes\n\n- Faster charts\n- New docs endpoint"
}'201 Created — same shape as charts; kind echoes the format:
{
"id": "p7q2m4xz",
"url": "https://app.embd.sh/c/p7q2m4xz",
"embedUrl": "https://app.embd.sh/embed/p7q2m4xz",
"iframe": "<iframe src=\"https://app.embd.sh/embed/p7q2m4xz\" ...></iframe>",
"title": "Release notes",
"kind": "markdown",
"status": "published"
}Lists everything you've created — charts and docs — newest first (the path and the charts response key are kept for compatibility). kind is the chart type for charts (bar, line, …) and the format for docs (html or markdown).
curl "$EMBD_API_URL/charts" -H "Authorization: Bearer $EMBD_API_KEY"
{
"charts": [
{
"id": "p7q2m4xz",
"title": "Release notes",
"kind": "markdown",
"status": "published",
"url": "https://app.embd.sh/c/p7q2m4xz",
"createdAt": "2026-07-07T09:30:00.000Z"
},
{
"id": "k3v9x2ab",
"title": "Q1 revenue vs cost",
"kind": "bar",
"status": "published",
"url": "https://app.embd.sh/c/k3v9x2ab",
"createdAt": "2026-07-06T12:00:00.000Z"
}
]
}Kind-agnostic: works on charts and docs alike.
curl -X DELETE "$EMBD_API_URL/charts/k3v9x2ab" -H "Authorization: Bearer $EMBD_API_KEY"
204 No Content. Only your own embeds are deletable; anything else is a 404.
The one route that needs no API key: it serves viewers of a protected embed, and the hosted pages (/c/:ref, /embed/:ref) call it under the hood. Passcode-protected embeds are invisible to anonymous reads — this endpoint exchanges the passcode for the embed payload. :ref is the 8-character id, falling back to the vanity slug. Fields:
passcode (required) — the passcode set by the embed's owner. Only its SHA-256 hash is stored server-side.curl -X POST "$EMBD_API_URL/embeds/k3v9x2ab/unlock" \
-H "Content-Type: application/json" \
-d '{"passcode": "opensesame"}'200 OK — the embed payload, and never the passcode hash or the owner:
{
"id": "k3v9x2ab",
"title": "Q1 revenue vs cost",
"kind": "chart",
"app_state": { "dataset": { ... }, "blocks": [ ... ] },
"theme_id": "aurora-dark",
"show_badge": true
}kind is the stored embed kind (chart, html, or markdown) and app_state is the stored state, exactly as the hosted viewer renders it. A wrong passcode (an empty one included) is 401 wrong_passcode. Anything that isn't a published, passcode-protected embed — missing, draft, or simply not protected (those are served by the normal public read) — is a uniform 404 not_found.
Errors are always { "error": { "code": "...", "message": "..." } }:
401 invalid_key — missing, malformed, unknown, or revoked API key.401 wrong_passcode — the unlock passcode didn't match (POST /embeds/:ref/unlock only).403 publish_cap — Free plan already has 5 published embeds (charts and docs both count). Unpublish one, pass "publish": false, or upgrade.403 save_cap — Free plan already has 50 saved embeds of any kind, drafts included. Delete one or upgrade.403 pro_required — slug is a Pro feature.400 invalid_request — bad body: unparseable data, unknown column/kind/theme/format, missing or oversized doc content, invalid or taken slug, oversized dataset or request body.404 not_found — unknown route or embed id.500 internal_error — something broke on our side; retry is safe.The caps are kind-agnostic — charts and docs draw from the same quota:
Pro: unlimited published and saved embeds, badge toggle, custom slugs. During the beta the API itself is free for everyone; after launch, API access will require Pro.