# Excalidraw documentation (/docs)
Welcome to Excalidraw Plus [#welcome-to-excalidraw-plus]
Looking to integrate Excalidraw Plus into your workflow? Whether you're building an integration with our API, connecting AI tools through MCP, planning to self-host, or exploring the open-source project, you're in the right place.
Excalidraw Plus brings powerful collaborative whiteboarding to your team with enterprise features, programmable access, and flexible deployment options.
Connect AI clients and agents using the Excalidraw MCP server. Let assistants
read, edit, and automate diagrams through MCP tools.
Build integrations with our RESTful API. Create, manage, and automate scenes,
collections, and workspaces programmatically.
Deploy Excalidraw Plus on your own infrastructure for data sovereignty,
private networking, and enterprise controls.
Running the open-source Excalidraw editor yourself? Start with the OSS
self-hosting overview and deployment guide.
Contributing to or integrating the open-source Excalidraw editor? Find
technical docs, architecture guides, and integration examples.
# Authentication (/docs/api/authentication)
API Key [#api-key]
The API playground stores your API keys in your browser's local storage for convenience.
**Important:** Use the playground only for testing. Do not use production keys, and remember to expire any test keys after use to keep your account secure.
Creating new key [#creating-new-key]
You can generate API keys in [Excalidraw Plus](https://app.excalidraw.com/) under the workspace settings.
Full key will be visible only once, so make sure to copy it and store it.
Each key can have different permissions based on the operation (`read`, `full`) or access to specific routes.
Keys also have specific expiration, after which it will no longer be valid.
Excalidraw+ supports personal/user API keys and workspace API keys. Personal
keys act as a specific workspace member and can access that member's virtual
private collection using the collection ID `private`. Workspace keys act as the
workspace and cannot list or access any member's private collection.
The private collection is built in. It cannot be created, renamed, or deleted,
and `POST /collections` always creates a regular shared collection. See
[Personal vs Workspace MCP/API Keys](/docs/mcp/mcp-api-key-types) for the full
behavior comparison.
Each key is associated with a workspace for which it is created and does not
have access to data from other workspaces.
Using the key [#using-the-key]
The API uses an API key sent in the `Authorization` header as `Bearer ` in all requests.
```js
const body = JSON.stringify({
name: "New scene",
});
fetch("https://api.excalidraw.com/api/v1/scenes", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer sk-...7qd", // <-- Your API key
},
body,
});
```
# Error Handling (/docs/api/error-handling)
The API uses standard HTTP status codes to indicate success or failure. Here's what each code means:
Common Status Codes [#common-status-codes]
| Status Code | Meaning | What to Check |
| ----------- | --------------------- | ----------------------------------------------------------------------- |
| `200` | Success | Request completed successfully |
| `400` | Bad Request | Check your request body format and required fields |
| `401` | Unauthorized | Verify your API key is correct and included in the Authorization header |
| `403` | Forbidden | Your API key doesn't have permission for this operation |
| `404` | Not Found | The resource doesn't exist or isn't accessible |
| `429` | Too Many Requests | You've hit the rate limit—slow down and retry |
| `500` | Internal Server Error | Something went wrong on our end—try again or contact support |
Error Response Format [#error-response-format]
Error responses include a message explaining what went wrong:
```json
{
"statusCode": 401,
"error": "Unauthorized",
"message": "Invalid API key"
}
```
Debugging Tips [#debugging-tips]
Having trouble? Here's what to check:
1. **Authentication issues (401)**: Make sure your API key is in the `Authorization` header as `Bearer YOUR_API_KEY`
2. **Permission issues (403)**: Check your API key permissions in workspace settings
3. **Bad requests (400)**: Verify your JSON is valid and includes all required fields
4. **Rate limits (429)**: Implement exponential backoff and monitor rate limit headers
# Getting Started (/docs/api/getting-started)
Want to explore your workspace? Here's how to make your first API requests in 3 steps:
The public API is now in public beta. Create an API key in workspace settings
and start integrating right away.
1\. Get Your API Key [#1-get-your-api-key]
Navigate to your workspace settings in [Excalidraw Plus](https://app.excalidraw.com/) and create an API key with the permissions you need.
Your API key will be visible only once—copy it somewhere safe! Learn more
about [API key management](/docs/api/authentication).
2\. List Your Collections [#2-list-your-collections]
Start by fetching your collections using curl:
```bash
curl https://api.excalidraw.com/api/v1/collections \
-H "Authorization: Bearer "
```
Or with JavaScript:
```javascript
const response = await fetch("https://api.excalidraw.com/api/v1/collections", {
headers: {
Authorization: "Bearer ",
},
});
const collections = await response.json();
console.log("Your collections:", collections);
```
3\. List Scenes in a Collection [#3-list-scenes-in-a-collection]
Now use a collection ID from the previous response to fetch its scenes:
```bash
curl https://api.excalidraw.com/api/v1/collections/COLLECTION_ID/scenes \
-H "Authorization: Bearer "
```
Or with JavaScript:
```javascript
const collectionId = collections.data[0].id; // Use first collection
const response = await fetch(
`https://api.excalidraw.com/api/v1/collections/${collectionId}/scenes`,
{
headers: {
Authorization: "Bearer ",
},
},
);
const scenes = await response.json();
console.log("Scenes in collection:", scenes);
```
Next Steps [#next-steps]
You've made your first API requests! Now explore more capabilities:
* **[Authentication](/docs/api/authentication)** - Learn about API key permissions and security
* **[Pagination](/docs/api/pagination)** - Handle large result sets efficiently
* **[Rate Limiting](/docs/api/rate-limiting)** - Stay within usage limits
* **[Endpoint Documentation](/docs/api/endpoints)** - Check the detailed docs below for all available operations
# Excalidraw API (/docs/api)
Excalidraw API is now in public beta. Endpoints, payloads, permissions,
response shapes, and overall behavior may still change as we continue to
stabilize the platform. Do not treat the current API contract as fully stable
yet.
The Excalidraw Plus API lets you work with scenes, collections, users, invites,
logs, and workspace resources programmatically.
Base URL [#base-url]
All API requests should be made to:
```text
https://api.excalidraw.com/api/v1
```
Quick start [#quick-start]
If you want to try the API immediately, start with the same flow as in
[Getting Started](/docs/api/getting-started):
1. Create an API key in your Excalidraw Plus workspace settings.
2. Use that key in the `Authorization` header.
3. Make your first request.
```bash
curl https://api.excalidraw.com/api/v1/collections \
-H "Authorization: Bearer "
```
What you can access [#what-you-can-access]
Manage workspace, users, and invites.
Manage collections of scenes.
Manage scenes, links, presentations, and scene content.
Beta notes [#beta-notes]
* The API is usable today, but it is not yet fully stable
* Naming, schemas, and supported flows may change during beta
* If you build on top of it now, expect breaking changes while we iterate
Next Steps [#next-steps]
Create your first API request and understand the setup flow.
Learn how to create and use API keys safely.
Understand current limits and integration best practices.
Debug common failures and understand API responses.
Work with paginated endpoints efficiently.
Understand the shared Excalidraw element format used by both API scene
content endpoints and MCP editing tools.
# Pagination (/docs/api/pagination)
Some endpoints return paginated results. We use offset-based pagination.
Request [#request]
To list all items, you can use the `offset` and `limit` query parameters:
| parameters | description | default value |
| ---------- | -------------------------------------- | ------------- |
| `offset` | The number of items to skip. | 0 |
| `limit` | The maximum number of items to return. | 10 |
Example:
```js
fetch("https://api.excalidraw.com/api/v1/scenes?offset=0&limit=10", {
method: "GET",
headers: {
Authorization: "Bearer sk-...7qd",
},
});
```
Response [#response]
A paginated response will return an array of items along with pagination metadata:
| field | description |
| ------------- | ----------------------------------------------------- |
| `data` | Array of returned data items. |
| `offset` | The number of items skipped in the response. |
| `limit` | The maximum number of items returned in the response. |
| `hasNextPage` | Whether there are more items to be fetched. |
Example response:
```json
{
"data": [
// array of returned data
],
"offset": 0,
"limit": 10,
"hasNextPage": true
}
```
# Rate Limiting (/docs/api/rate-limiting)
To ensure fair usage and system stability, the API is rate-limited to **600 requests per minute** per IP address.
Understanding Rate Limits [#understanding-rate-limits]
Each API response includes headers to help you track your rate limit status:
* `X-RateLimit-Limit`: Total requests allowed per minute (600)
* `X-RateLimit-Remaining`: Requests remaining in the current window
* `X-RateLimit-Reset`: Time when the rate limit resets (Unix timestamp)
Handling Rate Limits [#handling-rate-limits]
If you exceed the rate limit, you'll receive a `429 Too Many Requests` response. Here are some best practices:
* **Monitor the headers**: Check `X-RateLimit-Remaining` in your responses
* **Implement backoff**: When you get a 429, wait before retrying
* **Cache when possible**: Store responses that don't change frequently
* **Use pagination efficiently**: Request only the data you need
Example of handling rate limits in JavaScript:
```javascript
async function makeRequest(url) {
const response = await fetch(url, {
headers: { Authorization: "Bearer YOUR_API_KEY" },
});
if (response.status === 429) {
// Rate limited - wait and retry
const resetTime = response.headers.get("X-RateLimit-Reset");
const waitTime = resetTime * 1000 - Date.now();
await new Promise((resolve) => setTimeout(resolve, waitTime));
return makeRequest(url); // Retry
}
return response.json();
}
```
# Scene Content Schema (/docs/api/scene-content-schema)
This page describes the shared Excalidraw scene content format used by the
public API scene-content endpoints and MCP tools such as
`get_scene_content`, `search_scene_content`, `edit_scene_content`, and
the task-specific `read_diagram_format`, `read_presentation_format`, and
`read_freeform_format` guides.
This reference is derived from the same server-side schema used to validate
scene content writes plus the persisted Excalidraw element model. It is meant
to explain what the data means, not just list raw TypeScript fields.
It intentionally de-duplicates the large shared base across element types. The
goal is to stay close to the real schema without forcing you to read the same
base block repeated for every shape.
Whole scene payload [#whole-scene-payload]
Scene content is an Excalidraw document with scene-level metadata plus an array
of elements.
```json
{
"type": "excalidraw",
"version": 2,
"source": "https://plus.excalidraw.com",
"appState": {
"viewBackgroundColor": "#ffffff",
"lockedMultiSelections": {}
},
"elements": [
{
"id": "Q4x6Lh5y2C9vK8mN3pR1s",
"type": "rectangle",
"x": 120,
"y": 160,
"width": 280,
"height": 120,
"strokeColor": "#1e1e1e",
"backgroundColor": "#d0ebff",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roundness": { "type": 3 },
"roughness": 1,
"opacity": 100,
"angle": 0,
"seed": 123456789,
"version": 12,
"versionNonce": 987654321,
"index": "a1",
"isDeleted": false,
"groupIds": [],
"frameId": null,
"boundElements": null,
"updated": 1778156973482,
"link": null,
"locked": false
}
],
"sceneVersion": "15"
}
```
When a write operation includes image files, those files live in a separate
`files` object keyed by `fileId`.
Scene-level fields [#scene-level-fields]
* `type`: always `excalidraw`
* `version`: scene-content schema version
* `source`: source application or URL that produced the document
* `appState`: the subset of app state stored with the scene
* `elements`: array of persisted Excalidraw elements
* `sceneVersion`: scene-level version string used by the app for reconciliation
Stored appState fields [#stored-appstate-fields]
The validated write schema currently stores these app-state fields:
* `viewBackgroundColor`
* `lockedMultiSelections`
Example:
```json
{
"appState": {
"viewBackgroundColor": "#ffffff",
"lockedMultiSelections": {}
}
}
```
Files object [#files-object]
When image elements reference files, the payload can also include file records.
```json
{
"files": {
"file_123": {
"id": "file_123",
"mimeType": "image/png",
"created": 1778156973482,
"dataURL": "data:image/png;base64,..."
}
}
}
```
File records can include:
* `id`
* `mimeType`
* `created`
* `lastRetrieved` (optional)
* `version` (optional)
* `dataURL` for full file payloads
Common element fields [#common-element-fields]
Every persisted element type shares the same structural base:
* Identity: `id`, `type`
* Position and size: `x`, `y`, `width`, `height`, `angle`
* Visual style: `strokeColor`, `backgroundColor`, `fillStyle`, `strokeWidth`,
`strokeStyle`, `roundness`, `roughness`, `opacity`
* Reconciliation and ordering: `seed`, `version`, `versionNonce`, `index`,
`updated`
* Lifecycle: `isDeleted`, `locked`
* Grouping and framing: `groupIds`, `frameId`
* Cross-element links: `boundElements`, `link`
* Extension point: `customData`
What those fields mean [#what-those-fields-mean]
* `id`: opaque persisted element ID. Treat it as a stable identifier, not a
semantic name.
* `type`: the element family, such as `rectangle`, `text`, `arrow`, or `image`.
* `x`, `y`: top-left anchor in scene coordinates.
* `width`, `height`: rendered bounds in scene units.
* `angle`: element rotation in radians.
* `strokeColor`: outline color for shapes, line color for lines and arrows, and
glyph color for text.
* `backgroundColor`: fill color for closed shapes. Use `"transparent"` for no
fill.
* `fillStyle`: one of `hachure`, `cross-hatch`, `solid`, or `zigzag`.
* `strokeStyle`: one of `solid`, `dashed`, or `dotted`.
* `roundness`: `null` or an object such as `{ "type": 3 }` controlling corner
or path rounding.
* `roughness`: hand-drawn roughness level. Common values are `0`, `1`, and `2`.
* `opacity`: percentage from `0` to `100`.
* `angle`: stored in radians.
* `seed`: stable rendering seed.
* `version`: incremented when the element changes.
* `versionNonce`: random nonce used alongside `version` during reconciliation.
* `index`: fractional ordering key used to place the element in z/order.
* `groupIds`: nested group membership, ordered from deepest to shallowest.
* `frameId`: frame membership. This assigns membership only; it does not offset
child coordinates.
* `boundElements`: reverse references to elements attached to this one, usually
arrows or bound text.
* `updated`: last update timestamp in epoch milliseconds.
* `link`: optional hyperlink.
* `customData`: arbitrary app-specific metadata.
Exact shared object shapes [#exact-shared-object-shapes]
`roundness` [#roundness]
```json
{ "type": 3 }
```
Or:
```json
{ "type": 2, "value": 12 }
```
`boundElements` [#boundelements]
```json
[
{ "id": "someArrowId", "type": "arrow" },
{ "id": "someLabelId", "type": "text" }
]
```
`index` [#index]
`index` is either a fractional ordering string or `null` for newly created or
not-yet-indexed elements.
IDs on write vs persisted IDs [#ids-on-write-vs-persisted-ids]
The write schema accepts scene-scoped IDs as strings or numbers. If the IDs are
not already valid persisted Excalidraw IDs, the server normalizes them when it
can. Persisted scene content should be treated as canonical output.
Persisted element types [#persisted-element-types]
The persisted scene-content schema accepts these element types:
* `rectangle`
* `diamond`
* `ellipse`
* `embeddable`
* `frame`
* `magicframe`
* `iframe`
* `image`
* `text`
* `line`
* `arrow`
* `freedraw`
`selection` is an editor-only type and is not part of persisted scene content.
Shape elements [#shape-elements]
`rectangle`, `diamond`, `ellipse`, and `embeddable` share only the common base
fields above.
Use these when you need:
* regular containers or cards: `rectangle`
* decision-style shapes: `diamond`
* circular or soft rounded shapes: `ellipse`
* generic embedded surface placeholders: `embeddable`
Persisted rectangle example [#persisted-rectangle-example]
```json
{
"id": "rEcTaNgLeHiJkLmNoPqRs",
"type": "rectangle",
"x": 120,
"y": 160,
"width": 280,
"height": 120,
"strokeColor": "#1e1e1e",
"backgroundColor": "#d0ebff",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roundness": { "type": 3 },
"roughness": 1,
"opacity": 100,
"angle": 0,
"seed": 123456789,
"version": 12,
"versionNonce": 987654321,
"index": "a1",
"isDeleted": false,
"groupIds": [],
"frameId": null,
"boundElements": null,
"updated": 1778156973482,
"link": null,
"locked": false
}
```
Persisted diamond example [#persisted-diamond-example]
```json
{
"id": "dIaMoNdEfGhIjKlMnOpQr",
"type": "diamond",
"x": 480,
"y": 180,
"width": 180,
"height": 140,
"strokeColor": "#1e1e1e",
"backgroundColor": "#fff3bf",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roundness": null,
"roughness": 1,
"opacity": 100,
"angle": 0,
"seed": 441209331,
"version": 7,
"versionNonce": 287441903,
"index": "a2",
"isDeleted": false,
"groupIds": [],
"frameId": null,
"boundElements": null,
"updated": 1778156973482,
"link": null,
"locked": false
}
```
Persisted ellipse example [#persisted-ellipse-example]
```json
{
"id": "eLlIpSeEfGhIjKlMnOpQr",
"type": "ellipse",
"x": 720,
"y": 180,
"width": 200,
"height": 120,
"strokeColor": "#1e1e1e",
"backgroundColor": "#d3f9d8",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roundness": null,
"roughness": 1,
"opacity": 100,
"angle": 0,
"seed": 610448223,
"version": 9,
"versionNonce": 140338112,
"index": "a2V",
"isDeleted": false,
"groupIds": [],
"frameId": null,
"boundElements": null,
"updated": 1778156973482,
"link": null,
"locked": false
}
```
Frame elements [#frame-elements]
`frame` and `magicframe` add:
* `name: string | null`
Use frames for slide-like composition, named sections, or grouped canvas areas.
All child elements still use absolute scene coordinates; `frameId` only records
membership.
`magicframe` uses the same persisted shape as `frame`, but it represents a
specialized product concept rather than a separate structural schema family.
Persisted frame example [#persisted-frame-example]
```json
{
"id": "aBcDeFgHiJkLmNoPqRsTu",
"type": "frame",
"x": 100,
"y": 80,
"width": 854,
"height": 480,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roundness": { "type": 3 },
"roughness": 1,
"opacity": 100,
"angle": 0,
"seed": 218347651,
"version": 14,
"versionNonce": 551239001,
"index": "a3",
"isDeleted": false,
"groupIds": [],
"frameId": null,
"boundElements": null,
"updated": 1778156973482,
"link": null,
"locked": false,
"name": "Intro Slide"
}
```
Text elements [#text-elements]
`text` adds:
* `fontSize`
* `fontFamily` (stored as a numeric family ID)
* `text`
* `textAlign`: `left`, `center`, `right`
* `verticalAlign`: `top`, `middle`, `bottom`
* `containerId`
* `originalText`
* `autoResize`
* `lineHeight` (unitless)
Notes:
* `autoResize: true` means the text element width follows the text content.
* `autoResize: false` means the text wraps inside the current bounds.
* `originalText` preserves the raw authored text value.
* `lineHeight` must be multiplied by `fontSize` to estimate pixel line height.
Shape labels are stored as text elements [#shape-labels-are-stored-as-text-elements]
Persisted scene content does not store a shape label inline on the shape.
Instead:
* the shape lists the label in `boundElements`
* the label is a separate `text` element
* that text element points back to the shape with `containerId`
This matters because MCP `edit_scene_content` lets you use a higher-level
`label` helper, but the saved scene still becomes a normal `text` element plus
bindings.
Persisted text example [#persisted-text-example]
```json
{
"id": "tExTgHiJkLmNoPqRsTuVw",
"type": "text",
"x": 164,
"y": 128,
"width": 420,
"height": 72,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roundness": null,
"roughness": 1,
"opacity": 100,
"angle": 0,
"seed": 382940115,
"version": 8,
"versionNonce": 740191222,
"index": "a4",
"isDeleted": false,
"groupIds": [],
"frameId": "aBcDeFgHiJkLmNoPqRsTu",
"boundElements": null,
"updated": 1778156973482,
"link": null,
"locked": false,
"fontSize": 32,
"fontFamily": 1,
"text": "Quarterly Architecture Overview",
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Quarterly Architecture Overview",
"autoResize": true,
"lineHeight": 1.25
}
```
Image elements [#image-elements]
`image` adds:
* `fileId`
* `status`: `pending`, `saved`, or `error`
* `scale`: `[xScale, yScale]`
* `crop`
Notes:
* `fileId` should reference a file entry when the image is persisted.
* `scale` is used for mirroring or flipping an image.
* `crop` is either `null` or a crop rectangle with both displayed and natural
dimensions.
`crop` shape [#crop-shape]
```json
{
"x": 120,
"y": 40,
"width": 800,
"height": 450,
"naturalWidth": 1600,
"naturalHeight": 900
}
```
Persisted image example [#persisted-image-example]
```json
{
"id": "iMaGeFgHiJkLmNoPqRsTu",
"type": "image",
"x": 240,
"y": 220,
"width": 320,
"height": 180,
"strokeColor": "transparent",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roundness": null,
"roughness": 0,
"opacity": 100,
"angle": 0,
"seed": 640182993,
"version": 19,
"versionNonce": 208441771,
"index": "a7",
"isDeleted": false,
"groupIds": [],
"frameId": "aBcDeFgHiJkLmNoPqRsTu",
"boundElements": null,
"updated": 1778156973482,
"link": null,
"locked": false,
"fileId": "file_123",
"status": "saved",
"scale": [1, 1],
"crop": null
}
```
Iframe elements [#iframe-elements]
`iframe` uses the shared base schema. In practice, integrations often store
extra runtime metadata in `customData`, but the persisted schema treats that as
open-ended custom data rather than a fixed iframe-only contract.
The editor/runtime type model may also attach richer iframe-specific
`customData`, for example generation state objects. That is useful to know when
reading existing scenes, but it is intentionally not locked into the public
scene-content validation schema.
Persisted iframe example [#persisted-iframe-example]
```json
{
"id": "iFrAmEeFgHiJkLmNoPqRs",
"type": "iframe",
"x": 180,
"y": 560,
"width": 560,
"height": 315,
"strokeColor": "transparent",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 1,
"strokeStyle": "solid",
"roundness": { "type": 3 },
"roughness": 1,
"opacity": 100,
"angle": 0,
"seed": 380784165,
"version": 25,
"versionNonce": 829784709,
"index": "aE",
"isDeleted": false,
"groupIds": [],
"frameId": null,
"boundElements": null,
"updated": 1778156973482,
"link": "https://www.youtube.com/watch?v=kTRfKvzhxK8",
"locked": false
}
```
Line and arrow elements [#line-and-arrow-elements]
`line` and `arrow` both add:
* `points`
* `startBinding`
* `endBinding`
* `startArrowhead`
* `endArrowhead`
Bindings [#bindings]
Bindings attach a connector to another element and look like this:
```json
{
"elementId": "Q4x6Lh5y2C9vK8mN3pR1s",
"fixedPoint": [1, 0.5],
"mode": "inside"
}
```
* `elementId`: target element ID
* `fixedPoint`: normalized target position relative to the target width/height
* `mode`: `inside`, `orbit`, or `skip`
The binding target must be a bindable element such as a rectangle, diamond,
ellipse, text, image, iframe, embeddable, frame, or magicframe.
`fixedPoint` is a local ratio, not an absolute scene coordinate. Common useful
positions are:
* right edge: `[1, 0.5]`
* left edge: `[0, 0.5]`
* top edge: `[0.5, 0]`
* bottom edge: `[0.5, 1]`
Arrowheads [#arrowheads]
Supported arrowhead values are:
* `arrow`
* `bar`
* `circle`
* `circle_outline`
* `triangle`
* `triangle_outline`
* `diamond`
* `diamond_outline`
* `cardinality_one`
* `cardinality_many`
* `cardinality_one_or_many`
* `cardinality_exactly_one`
* `cardinality_zero_or_one`
* `cardinality_zero_or_many`
* `null`
Line-specific and arrow-specific fields [#line-specific-and-arrow-specific-fields]
* `line` may use `polygon: boolean`
* `arrow` may use `elbowed: boolean`
* elbow arrows may also include `fixedSegments`, `startIsSpecial`, and
`endIsSpecial`
Elbow-arrow fields [#elbow-arrow-fields]
When `elbowed` is `true`, an arrow may also carry:
* `fixedSegments`: explicit routed segments
* `startIsSpecial`
* `endIsSpecial`
`fixedSegments` looks like this:
```json
[
{
"start": [0, 0],
"end": [120, 0],
"index": 0
}
]
```
These are advanced routing details. Most integrations should preserve them when
editing an existing elbow arrow unless they intentionally want to reroute it.
Persisted line example [#persisted-line-example]
```json
{
"id": "lInEeFgHiJkLmNoPqRsTu",
"type": "line",
"x": 160,
"y": 360,
"width": 260,
"height": 60,
"strokeColor": "#495057",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "dashed",
"roundness": null,
"roughness": 1,
"opacity": 100,
"angle": 0,
"seed": 551102334,
"version": 5,
"versionNonce": 223401992,
"index": "b0",
"isDeleted": false,
"groupIds": [],
"frameId": null,
"boundElements": null,
"updated": 1778156973482,
"link": null,
"locked": false,
"points": [[0, 0], [80, 20], [160, 20], [260, 60]],
"startBinding": null,
"endBinding": null,
"startArrowhead": null,
"endArrowhead": null,
"polygon": false
}
```
Persisted arrow example [#persisted-arrow-example]
```json
{
"id": "aRrOwFgHiJkLmNoPqRsTu",
"type": "arrow",
"x": 400,
"y": 220,
"width": 260,
"height": 120,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roundness": { "type": 2 },
"roughness": 1,
"opacity": 100,
"angle": 0,
"seed": 938441250,
"version": 11,
"versionNonce": 398440211,
"index": "b1",
"isDeleted": false,
"groupIds": [],
"frameId": null,
"boundElements": null,
"updated": 1778156973482,
"link": null,
"locked": false,
"points": [[0, 0], [260, 120]],
"startBinding": {
"elementId": "Q4x6Lh5y2C9vK8mN3pR1s",
"fixedPoint": [1, 0.5],
"mode": "inside"
},
"endBinding": {
"elementId": "nOdE2FgHiJkLmNoPqRsTu",
"fixedPoint": [0, 0.5],
"mode": "inside"
},
"startArrowhead": null,
"endArrowhead": "triangle",
"elbowed": false
}
```
Freedraw elements [#freedraw-elements]
`freedraw` adds:
* `points`
* `pressures`
* `simulatePressure`
Use this for pen-like strokes where the shape is defined by sampled points
rather than a simple box or connector path.
In practice, `pressures` usually tracks the point sequence and `simulatePressure`
controls whether the stroke should synthesize pressure-like variation.
Persisted freedraw example [#persisted-freedraw-example]
```json
{
"id": "fReEdRaWjKkLmNoPqRsTu",
"type": "freedraw",
"x": 120,
"y": 420,
"width": 180,
"height": 64,
"strokeColor": "#e03131",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roundness": null,
"roughness": 1,
"opacity": 100,
"angle": 0,
"seed": 111281902,
"version": 6,
"versionNonce": 503819441,
"index": "b4",
"isDeleted": false,
"groupIds": [],
"frameId": null,
"boundElements": null,
"updated": 1778156973482,
"link": null,
"locked": false,
"points": [[0, 0], [24, 8], [52, 18], [95, 35], [180, 64]],
"pressures": [0.3, 0.4, 0.55, 0.5, 0.45],
"simulatePressure": false
}
```
Reference integrity rules [#reference-integrity-rules]
The shared validation schema enforces a few important relationships:
* `frameId` must point to a `frame` or `magicframe`
* `containerId` must point to `rectangle`, `diamond`, `ellipse`, or `arrow`
* bound text must point back to its container correctly
* bound arrows must point back to their bound shape correctly
* image `fileId` values must reference a file when files are present
It also constrains relation object shapes, for example:
* `boundElements[].type` must be `arrow` or `text`
* binding `mode` must be `inside`, `orbit`, or `skip`
* line and arrow `points` must be arrays of finite numeric tuples
API and MCP differences [#api-and-mcp-differences]
The persisted element format is shared, but the authoring ergonomics differ:
* REST scene-content endpoints work with full scene content objects and raw
element arrays.
* MCP `edit_scene_content` works with higher-level `add`, `update`, and `delete`
operations.
* MCP provides task-specific adapters: `create_diagram` for automatic graph
layout, `create_slide`/`update_slide`/`list_slides` for presentations,
`add_image` for image ingestion, and `take_screenshot` for visual verification.
MCP also supports helper concepts that are not persisted as final element
fields:
* `tempId` for same-request references between newly added elements
* `label` for shape-owned text expansion
* high-level add/update/delete operation grouping
Those helpers are conveniences at write time only. Persisted scene content still
uses normal element IDs, `text` nodes, `containerId`, and `boundElements`.
What this page does not try to freeze [#what-this-page-does-not-try-to-freeze]
This page describes the shared persisted scene-content contract and its main
authoring semantics. It does not try to freeze every editor-internal runtime
detail that may exist in upstream Excalidraw types, especially when those
details are not enforced by the public validation schema.
Examples:
* editor-only `selection` elements
* open-ended `customData` contents
* runtime-only convenience helpers used by MCP writes
* implementation details of rendering, reconciliation, or layout helpers outside
the persisted content contract
Practical guidance [#practical-guidance]
* Treat IDs, versions, nonces, and index keys as opaque fields.
* Prefer rectangle, diamond, ellipse, frame, and text as your main semantic
building blocks.
* Use text containers and bindings instead of inventing a custom inline label
shape format.
* Use arrow bindings when a connector should stay attached to a shape.
* Use `frameId` for grouping into frames, not for relative positioning.
Related docs [#related-docs]
* [Scene Content endpoints](/docs/api/scene-content)
* [Authentication](/docs/api/authentication)
* [MCP Tools](/docs/mcp/tools)
* [MCP Getting Started](/docs/mcp/getting-started)
# Auth and Permissions (/docs/mcp/auth-and-permissions)
MCP authentication uses the same API key system as the public API.
Authentication [#authentication]
* Send your key as `Authorization: Bearer `
* Missing or invalid keys return `401`
* Keys are workspace-scoped, so tools only access that workspace data
Permission model [#permission-model]
MCP does not bypass API permissions. Each MCP tool is mapped to a public API
route + method, and tool access is granted only when that route is allowed for
the key.
In practice, this means:
* Read-only keys expose read-only tools
* Full-access keys expose create/update/delete tools
* Route-restricted keys expose only matching tools
Recommended setup [#recommended-setup]
* Use separate keys for MCP and direct API traffic
* Start with minimum permissions, then expand as needed
* Rotate keys regularly and expire test keys
For full key management guidance, see [API Authentication](/docs/api/authentication).
# Getting Started (/docs/mcp/getting-started)
Prerequisites [#prerequisites]
1. An Excalidraw Plus workspace
2. A public API key from workspace settings
3. An MCP client that supports Streamable HTTP
Excalidraw+ MCP is now in public beta and built on top of the public API.
Contracts, tool names, schemas, and behavior may still change.
If you want the open source MCP server instead, see
[`excalidraw/excalidraw-mcp`](https://github.com/excalidraw/excalidraw-mcp).
We may merge the open source and Excalidraw+ MCP efforts more closely later.
1\. Create an API key [#1-create-an-api-key]
Create a key in Excalidraw Plus with only the permissions you need. MCP tools
are filtered by the same route permissions as the public API.
If you are not sure which MCP/API key type to use, read
[Personal vs Workspace MCP/API Keys](/docs/mcp/mcp-api-key-types). In short:
personal keys are usually best for agents acting like you, while workspace keys
are better for shared integrations.
Learn more in [API Authentication](/docs/api/authentication).
2\. Configure your MCP client [#2-configure-your-mcp-client]
Set the MCP server URL:
```text
https://api.excalidraw.com/api/v1/mcp
```
And send your key in the `Authorization` header:
```text
Authorization: Bearer
```
The endpoint and auth are the same across clients, but each client uses its own
config shape. Pick your client and paste the matching config into its MCP
settings/config UI.
Use this in Cursor MCP settings:
```json
{
"mcpServers": {
"excalidraw": {
"url": "https://api.excalidraw.com/api/v1/mcp",
"headers": {
"Authorization": "Bearer "
}
}
}
}
```
Use this in `.vscode/mcp.json` or your VS Code user `mcp.json`:
```json
{
"servers": {
"excalidraw": {
"type": "http",
"url": "https://api.excalidraw.com/api/v1/mcp",
"headers": {
"Authorization": "Bearer "
}
}
}
}
```
Use this in your OpenCode config under `mcp`:
```json
{
"mcp": {
"excalidraw": {
"type": "remote",
"url": "https://api.excalidraw.com/api/v1/mcp",
"enabled": true,
"headers": {
"Authorization": "Bearer "
}
}
}
}
```
Use this in project-scoped `.mcp.json` for Claude Code:
```json
{
"mcpServers": {
"excalidraw": {
"type": "http",
"url": "https://api.excalidraw.com/api/v1/mcp",
"headers": {
"Authorization": "Bearer "
}
}
}
}
```
Codex uses `config.toml`, not JSON. Add this to `~/.codex/config.toml` or a
project-scoped `.codex/config.toml`:
```toml
[mcp_servers.excalidraw]
url = "https://api.excalidraw.com/api/v1/mcp"
env_http_headers = { Authorization = "EXCALIDRAW_API_KEY" }
```
Then set:
```bash
export EXCALIDRAW_API_KEY="Bearer "
```
If your client expects the more common `mcpServers` JSON shape, use this:
```json
{
"mcpServers": {
"excalidraw": {
"url": "https://api.excalidraw.com/api/v1/mcp",
"headers": {
"Authorization": "Bearer "
}
}
}
}
```
3\. Verify tool access [#3-verify-tool-access]
After connecting, ask your client to list available tools. The list depends on
the permissions assigned to your API key.
If a tool is missing, update key permissions first, then reconnect.
Troubleshooting [#troubleshooting]
* `401 Unauthorized`: missing or invalid `Authorization` header
* Tool not visible: API key lacks permission for the underlying route
* `405 Method Not Allowed`: use `POST /api/v1/mcp` only (stateless mode)
# Excalidraw+ MCP (/docs/mcp)
Excalidraw+ MCP is now in public beta. It is built on top of the Excalidraw+
API, and everything may still change while we stabilize both surfaces. Tool
names, schemas, auth behavior, and overall integration patterns should not be
treated as fully stable yet.
Excalidraw+ MCP lets AI clients access your Excalidraw Plus workspace through
the Model Context Protocol (MCP).
If you are looking for the open source MCP server, see
[`excalidraw/excalidraw-mcp`](https://github.com/excalidraw/excalidraw-mcp).
We may merge the Excalidraw+ and open source MCP efforts more closely over
time.
Endpoint [#endpoint]
```text
https://api.excalidraw.com/api/v1/mcp
```
Quick install [#quick-install]
To connect an MCP client, follow [Getting Started](/docs/mcp/getting-started):
1. Create an Excalidraw Plus API key.
2. Point your MCP client at the Excalidraw+ MCP endpoint.
3. Send the API key as a bearer token.
How it works [#how-it-works]
* Transport: Streamable HTTP
* Auth: `Authorization: Bearer `
* Mode: Stateless (no long-lived server session)
* Permissions: tools are filtered by the same API key permissions as the API
Beta notes [#beta-notes]
* MCP behavior may change as the underlying API evolves
* Tool names, schemas, and output formats may change during beta
* If you need the most stable integration path today, use the REST API directly
Next Steps [#next-steps]
* [Getting Started](/docs/mcp/getting-started)
* [Auth and Permissions](/docs/mcp/auth-and-permissions)
* [Personal vs Workspace MCP/API Keys](/docs/mcp/mcp-api-key-types)
* [Tools](/docs/mcp/tools)
* [Scene Content Schema](/docs/api/scene-content-schema)
# Personal vs Workspace MCP/API Keys (/docs/mcp/mcp-api-key-types)
Excalidraw+ has two different key types you may encounter in settings:
* `Personal MCP/API keys`
* `Workspace API/MCP keys`
They are both valid for API and MCP access, but they are meant for different jobs.
Use a personal MCP/API key when an AI agent or script should act like
you. Use a workspace API/MCP key when you are building a
system integration that should act on behalf of the workspace instead
of a single person.
At a glance [#at-a-glance]
| Key type | Best for | Acts like | Private collection access |
| --------------------- | ---------------------------------------------- | ------------------------- | ------------------------- |
| Personal MCP/API key | Agents, assistants, personal scripts | A specific member | Yes |
| Workspace API/MCP key | n8n, Zapier, backend jobs, shared integrations | The workspace/integration | No |
Personal MCP/API keys [#personal-mcpapi-keys]
Use a personal key when the automation should behave like a real member inside
Excalidraw+.
Personal MCP/API keys are not always available by default. A workspace admin
must enable personal MCP/API keys in workspace settings before members can
create or use them.
Typical examples:
* Connecting AI coding agents or assistants
* Personal MCP setups in tools like Cursor, Claude Code, OpenCode, or Codex
* Small scripts that should use your own access and context
Why choose a personal key:
* It is tied to a specific member
* It can access that member's private collection
* It is usually the best choice when the agent is helping one person directly
If your goal is "make this agent work like me inside Excalidraw+", start with a
personal key.
Workspace API/MCP keys [#workspace-apimcp-keys]
Use a workspace key when the integration should represent the workspace rather
than one specific member.
Typical examples:
* n8n workflows
* Zapier automations
* Internal backend services
* Shared integration servers or bots
Why choose a workspace key:
* It is better suited for long-lived system integrations
* It keeps the integration separate from one person's account
* It does not access any member's private collection
If your goal is "connect this workspace to another system", start with a
workspace key.
Main difference: private collection access [#main-difference-private-collection-access]
The biggest behavioral difference is private data access:
* Personal MCP/API keys can access the key owner's private collection
* Workspace API/MCP keys cannot access members' private collections
This is why personal keys are usually better for assistants and agents, while
workspace keys are safer and clearer for shared automations.
Using the private collection [#using-the-private-collection]
With a personal key, use `private` as the collection ID to address the key
owner's virtual private collection. This works consistently across REST and MCP:
* `list_collections` includes a collection named `Private` with ID `private`
* `list_scenes` and `list_collection_scenes` accept `collectionId: "private"`
* `create_scene` and `create_collection_scene` accept `collectionId: "private"`
* `update_scene` accepts `collectionId: "private"` when moving one of your own
scenes into your private collection
API responses normalize private scenes to `collection: "private"`. Internally,
private scenes are identified by having no persisted collection
(`collection: null`); clients should use the normalized `private` collection ID
rather than depending on the `isPrivate` compatibility field.
Each member has exactly one virtual private collection. It cannot be created,
renamed, or deleted. `create_collection` and `POST /collections` always create a
regular shared collection; they cannot create another private collection.
Workspace keys do not list the virtual private collection and cannot use
`private` to read, create, or move scenes.
Which one should I choose? [#which-one-should-i-choose]
Choose a personal key if:
* The tool is helping one person
* The agent should see that person's private work
* You want the integration to behave like a member session
Choose a workspace key if:
* The integration is shared by a team
* The workflow should not depend on one person's account
* You are building operational automations or external system syncs
Related docs [#related-docs]
* [MCP Getting Started](/docs/mcp/getting-started)
* [MCP Auth and Permissions](/docs/mcp/auth-and-permissions)
* [API Authentication](/docs/api/authentication)
# Tools (/docs/mcp/tools)
The MCP server exposes both public-API-backed tools and MCP-only adapter tools.
Scenes [#scenes]
* `list_scenes`
* `create_scene`
* `get_scene`
* `update_scene`
* `delete_scene`
* `search_scene_content`
* `get_scene_content`
* `edit_scene_content`
`search_scene_content` is the recommended read tool when an agent only needs to
locate shapes, labels, or text without loading the entire scene payload. It
returns full matching Excalidraw element nodes.
Its default `contains` mode is case-insensitive and ignores common separators
such as spaces, hyphens, underscores, dots, commas, colons, semicolons,
slashes, and backslashes. For example, `Auth Flow`, `auth-flow`, `auth_flow`,
and `authflow` will all match each other.
Use `glob` only when you intentionally want wildcard matching with `*` and `?`.
Use `get_scene_content` only when you need the full scene payload instead of
just the matching nodes.
`edit_scene_content` is the higher-level MCP write tool for add, update, and
delete operations, including label expansion and bound-text handling.
Prefer `label` over standalone `text` when the text belongs to a shape.
Use standalone `text` only for titles, subtitles, paragraphs, and intentionally
separate annotations.
For buttons, badges, cards, and boxed callouts, prefer rectangle `label` text
instead of overlaying standalone `text`.
Use `\n` for intentional standalone text wrapping, and give text elements extra
`width` and `height`. Avoid exact or tight text bounds; clipping means the bounds
are too small, not that the text value is missing.
Prefer rectangles/cards for text-bearing content blocks. Use diamonds mainly
for decision nodes in diagrams, not for dense or multiline content.
For `add`, send a JSON array string of new element skeletons and do not include
`id`. The server generates persisted IDs for newly added elements.
Use `tempId` only for same-request references between newly added elements,
such as `frameId`, `containerId`, `startBinding.elementId`, and
`endBinding.elementId`.
Use real persisted IDs for `update`, `delete`, or when referencing elements
that already exist in the scene.
Arrows that point at shapes must include `startBinding` and/or `endBinding`.
Arrow geometry alone is decorative and will not stay attached. For same-request
shape-to-shape arrows, add `tempId` to both target shapes and reference those
tempIds from `startBinding.elementId` and `endBinding.elementId`. Use fixed
points such as right `[1, 0.5]`, left `[0, 0.5]`, top `[0.5, 0]`, and bottom
`[0.5, 1]`.
Same-request tempId arrow bindings are supported; the MCP adapter keeps target
shape `boundElements` in sync, so agents do not need a separate call just to get
real IDs.
For label updates, always send `label` as an object such as
`{ "text": "New Label" }`, not as a plain string.
`edit_scene_content` applies operations in this order: `delete`, then
`update`, then `add`.
The low-level REST scene-content write endpoints still exist in the public API,
but they are intentionally not exposed as MCP tools. Use
`edit_scene_content` for MCP writes.
For element and scene-content reference, see
[Scene Content Schema](/docs/api/scene-content-schema).
Format guides [#format-guides]
* `read_diagram_format`
* `read_presentation_format`
* `read_freeform_format`
Call the guide matching the task before the first related scene write in a
session. Diagram and presentation guides describe their specialized workflows;
the freeform guide covers annotations, sticky notes, moodboards, wireframes,
charts, and other custom compositions. These tools replace the former
monolithic format guide.
Diagrams [#diagrams]
* `create_diagram`
`create_diagram` builds editable diagrams from semantic nodes, edges, and
optional groups. It measures labels, lays out the graph, routes bound elbow
arrows, and reserves room for edge labels. Use `DOWN` for workflows and
flowcharts or `RIGHT` for architecture maps and pipelines. Use
`edit_scene_content` for follow-up visual tweaks or layouts such as swimlanes
and timelines that require custom positioning.
Presentations [#presentations]
* `create_slide`
* `update_slide`
* `list_slides`
Use one frame per slide. `create_slide` positions and orders the frame and
returns its ID, bounds, safe area, and suggested layout regions. Fill that frame
with `edit_scene_content`, setting every child element's `frameId` and using
absolute canvas coordinates inside the returned safe area. Use `update_slide`
for titles, presenter notes, and slide order so frame metadata is merged safely.
Use `list_slides` to review ordered slides, bounds, safe areas, notes, and
element counts.
Images and verification [#images-and-verification]
* `add_image`
* `take_screenshot`
`add_image` accepts one public URL or base64 data URL, supports PNG, JPEG, GIF,
WebP, and SVG, and can place the resulting editable image inside a slide frame.
It detects natural dimensions and preserves the aspect ratio when only one
render dimension is supplied.
`take_screenshot` renders the full scene or one frame as PNG. Use it after
substantial scene writes to inspect visual correctness and fix clipping,
overlaps, routing, or spacing problems.
Collections [#collections]
* `list_collections`
* `create_collection`
* `get_collection`
* `update_collection`
* `delete_collection`
* `list_collection_scenes`
* `create_collection_scene`
Personal MCP/API keys can use `private` as the collection ID to list, create,
and move scenes in the key owner's virtual private collection. Workspace keys
cannot access it. The private collection already exists virtually and cannot be
created, renamed, or deleted; `create_collection` always creates a regular
shared collection.
Workspace [#workspace]
* `get_workspace`
* `update_workspace`
Users [#users]
* `list_workspace_users`
* `get_workspace_user`
* `update_workspace_user`
* `remove_workspace_user`
Invites [#invites]
* `list_invites`
* `create_email_invite`
* `create_invite_link`
* `get_invite`
* `update_invite`
* `delete_invite`
Use `create_email_invite` for a specific recipient. Use `create_invite_link`
for a reusable link with optional usage and domain restrictions.
Logs [#logs]
* `list_logs`
Your MCP client may show fewer tools than listed above if your API key does
not include permissions for the underlying routes. MCP-only tools still follow
the same route permission checks.
# Excalidraw Open Source Self-Hosting (/docs/self-hosting/excalidraw-open-source-selfhosting)
Self-Host the Open-Source Excalidraw Editor [#self-host-the-open-source-excalidraw-editor]
If you want to run the open-source Excalidraw editor on your own infrastructure,
use the official Excalidraw self-hosting documentation.
This path is best for teams that want to deploy the Excalidraw editor itself
without the broader Excalidraw Plus platform or hosted workspace features.
When to Use Open Source Self-Hosting [#when-to-use-open-source-self-hosting]
The open-source route is a good fit if you need the editor itself and want to
manage the deployment independently without the full Excalidraw Plus platform.
Common Reasons to Self-Host the Open-Source Editor [#common-reasons-to-self-host-the-open-source-editor]
* You want a self-hosted Excalidraw editor for internal tools
* You are embedding Excalidraw into your own product
* You want full control over infrastructure and deployment
* You do not need the broader Excalidraw Plus feature set
Official Guide [#official-guide]
[Open the official Excalidraw self-hosting guide](https://docs.excalidraw.com/docs/introduction/development#self-hosting)
Need the Full Platform Instead? [#need-the-full-platform-instead]
If you need team workspaces, enterprise controls, and the broader Excalidraw
Plus platform, see [Excalidraw Plus
Self-Hosting](/docs/self-hosting/excalidraw-plus-self-hosting).
# Excalidraw Plus Self-Hosting (/docs/self-hosting/excalidraw-plus-self-hosting)
Self-hosting for Excalidraw Plus is in progress. It will be available as part
of the Excalidraw Enterprise license.
Self-Host Excalidraw Plus [#self-host-excalidraw-plus]
Excalidraw Plus self-hosting is designed for teams that need collaborative
whiteboarding on their own infrastructure. It is a strong fit for organizations
with strict security, compliance, networking, or data residency requirements.
Why Self-Host? [#why-self-host]
* Keep whiteboard data inside your infrastructure
* Run in private or restricted networks
* Align with internal security and compliance controls
* Integrate with enterprise authentication and SSO
* Manage upgrades, backups, and retention on your terms
Planned Coverage [#planned-coverage]
* Excalidraw Plus application deployment
* Real-time collaboration services
* Authentication and SSO guidance
* Storage and infrastructure configuration
* Backup, upgrade, and operational guidance
Open Source or Plus? [#open-source-or-plus]
If you only need the open-source Excalidraw editor, the OSS self-hosting path
may be enough. If you need the full Excalidraw Plus platform with team
workspaces, enterprise controls, and a broader hosted product surface,
Excalidraw Plus self-hosting is the better fit.
Looking for the Open-Source Editor? [#looking-for-the-open-source-editor]
If you want to self-host the open-source Excalidraw editor instead of the full
Excalidraw Plus platform, see [Excalidraw Open Source
Self-Hosting](/docs/self-hosting/excalidraw-open-source-selfhosting).
# Self-Hosting (/docs/self-hosting)
Choose the deployment path that matches your setup. If you need the full
Excalidraw Plus platform for your organization, start with Excalidraw Plus
self-hosting. If you only need the open-source editor, use the OSS guide.
Run the full Excalidraw Plus platform on your own infrastructure.
Self-host the open-source Excalidraw editor with the official deployment
guide.
# API Endpoints (/docs/api/endpoints)
Collections
Invites
Logs
Scene Content
Scenes
Users
Workspace
# Delete collection (/docs/api/collections/collectionId-delete)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Soft-delete a collection by moving it to trash.
This endpoint does not permanently delete the collection. It performs a trash move (soft delete).
**Use cases:**
* Moving unused collections to trash
* Implementing collection management workflows
* Lifecycle management before permanent deletion
# Get collection by ID (/docs/api/collections/collectionId-get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve detailed information about a specific collection by its ID.
This endpoint returns all metadata for the collection, including its name and configuration.
**Use cases:**
* Displaying collection details in UI
* Validating collection existence before creating scenes
* Fetching collection metadata for integrations
# Update collection (/docs/api/collections/collectionId-patch)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Update the metadata of an existing collection by its ID.
You can update the collection name or other editable fields.
**Use cases:**
* Renaming collections
* Managing collections in admin dashboards
* Bulk updating collection metadata
# Get scenes in collection (/docs/api/collections/collectionId-scenes-get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve all scenes that belong to a specific collection.
This endpoint allows you to:
* List all scenes within a collection
* Paginate through scenes for large collections
* Access scene metadata and sharing links
**Use cases:**
* Displaying collection contents in UI
* Filtering scenes by collection
* Building collection detail pages
# Create scene in collection (/docs/api/collections/collectionId-scenes-post)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a new scene within a specific collection.
This endpoint creates a scene with the specified metadata and assigns it to the given collection.
**Use cases:**
* Adding new scenes to a project or team collection
* Automating scene creation for workflows
* Integrating with external tools to populate collections
# Get collections (/docs/api/collections/get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve a paginated list of all collections visible to the API key.
Personal API keys receive the key owner's virtual Private collection with ID `private`. Workspace API keys never receive private collections.
This endpoint allows you to:
* List all collections available to the current workspace
* Paginate through large sets of collections
* Use for collection pickers, dashboards, or management UIs
**Use cases:**
* Displaying available collections to users
* Building collection management interfaces
* Syncing collections with external tools
# Collections (/docs/api/collections)
# Create collection (/docs/api/collections/post)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a new collection in the workspace.
This endpoint creates a collection with the specified name. Collections are used to organize scenes and manage permissions.
**Use cases:**
* Organizing scenes by project, team, or topic
* Automating collection creation for onboarding flows
* Integrating with external project management tools
# Invites (/docs/api/invites)
# Get workspace invites (/docs/api/invites/workspaces-invites-get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve a paginated list of invites for the current workspace.
This endpoint allows you to:
* List all pending and active invites
* Paginate through large invite lists
**Use cases:**
* Managing workspace invitations
* Displaying invite status in UI
* Auditing workspace access
# Delete workspace invite (/docs/api/invites/workspaces-invites-inviteId-delete)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Delete a specific workspace invite by its ID.
Deleting an invite will prevent any further use of the invite link or email.
**Use cases:**
* Revoking unused or compromised invites
* Managing workspace access
* Cleaning up expired invites
# Get workspace invite (/docs/api/invites/workspaces-invites-inviteId-get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve details for a specific workspace invite by its ID.
This endpoint returns all metadata for the invite, including status and restrictions.
**Use cases:**
* Displaying invite details in UI
* Validating invite status before accepting
* Auditing invite usage
# Update workspace invite (/docs/api/invites/workspaces-invites-inviteId-patch)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Update the metadata of a specific workspace invite by its ID.
You can update:
* Email address (for email invites)
* Role assigned to the invitee
* Maximum uses or domain restrictions
**Use cases:**
* Correcting invite details
* Changing invite permissions
* Managing invite limits
# Create invite (/docs/api/invites/workspaces-invites-post)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a new invite for the workspace.
You can create:
* Email-based invites (send to a specific email)
* Invite links (with optional restrictions and usage limits)
**Use cases:**
* Inviting new members to the workspace
* Generating invite links for onboarding
* Automating team invitations
# Get workspace logs (/docs/api/logs/get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve a paginated list of audit logs for your workspace.
This endpoint allows you to:
* Get all audit logs in your workspace
* Filter logs by user, action, operation, or date range
* Paginate through large sets of logs using cursor-based or offset-based pagination
**Use cases:**
* Building an audit log viewer or dashboard
* Monitoring workspace activity
* Compliance and security auditing
* Tracking user actions and changes
**Available log actions:**
* workspace, workspace:team, workspace:invite, workspace:invite:redeem, workspace:preferences, workspace:avatar, workspace:rename, workspace:user, workspace:user:role
* collection
* scene, scene:readonly-link, scene:readonly-links, scene:link-sharing, scene:collection, scene:upload, scene:image-upload, scene:slides, scene:comment
* user:signup
* ai:diagram-to-code, ai:text-to-diagram, ai:text-to-drawing
# Logs (/docs/api/logs)
# Scene Content (/docs/api/scene-content)
# Get scene content (/docs/api/scene-content/scenes-sceneId-content-get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve the complete content of a scene including all drawing elements and files.
This endpoint returns:
* All drawing elements (shapes, lines, text, etc.)
* Embedded files and images
* Scene settings and configuration
* Version information
* Canvas state and viewport settings
The response includes the current version of the scene. For version history or specific versions, use the versioning endpoints.
Large scenes with many elements or files may result in substantial response sizes. Consider implementing client-side caching for frequently accessed scenes.
**Use cases:**
* Loading scenes for editing
* Creating scene backups
* Exporting scene data
* Scene analysis and processing
# Patch scene content (/docs/api/scene-content/scenes-sceneId-content-patch)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Patch scene content by merging supplied fields into the existing scene instead of replacing it.
Unlike the PUT endpoint which replaces all content, PATCH performs a server-side merge:
* **Elements**: Merged by element ID using version-based reconciliation. Higher version wins; equal versions are resolved by versionNonce tie-breaking. Elements not included in the request are preserved. Send elements with `isDeleted: true` to soft-delete them.
* **App State**: Shallow merge of provided fields over existing state.
* **Files**: New files are added; existing files are preserved unless replaced by a file with the same ID.
PATCH accepts a partial scene-content object. You may provide any subset of `elements`, `appState`, and `files`, but at least one of them must be present. This endpoint does not perform an authoritative replacement and does not force connected editors to reload. It writes a merged scene using the current stored content as the base. If the scene is modified concurrently, the merge result may temporarily diverge until the next editor save/reconciliation cycle. If you want to replace the entire scene with a new authoritative version, use `PUT /scenes/:sceneId/content` instead.
**Use cases:**
* Adding or updating specific elements without affecting others
* Updating background color without touching elements
* Programmatically adding images/files to an existing scene
* Building integrations that modify scenes without full content replacement
# Replace scene content (/docs/api/scene-content/scenes-sceneId-content-put)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Replace the complete content of a scene with the supplied elements, files, and app state.
This endpoint allows you to:
* Replace all scene elements with new ones
* Update or add files to the scene
* Modify scene settings and app state
* Force connected editors to reload the scene after an authoritative full replacement
This is an authoritative full replacement. Any existing elements not included in the request will be removed from the scene, connected editors will be forced to reload instead of reconciling the change incrementally, and any `sceneVersion` value in the request body will be ignored because the server always recomputes it from the submitted content. If you only want to add or update specific elements, files, or app-state fields without replacing the whole scene, use `PATCH /scenes/:sceneId/content` instead.
**Use cases:**
* Restoring scene from backup
* Replacing a scene from an external system of record
* Correcting a scene even while editors are currently open
* Programmatic scene generation
# Get scenes in collection (/docs/api/scenes/collections-collectionId-scenes-get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve all scenes that belong to a specific collection.
This endpoint allows you to:
* List all scenes within a collection
* Paginate through scenes for large collections
* Access scene metadata and sharing links
**Use cases:**
* Displaying collection contents in UI
* Filtering scenes by collection
* Building collection detail pages
# Create scene in collection (/docs/api/scenes/collections-collectionId-scenes-post)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a new scene within a specific collection.
This endpoint creates a scene with the specified metadata and assigns it to the given collection.
**Use cases:**
* Adding new scenes to a project or team collection
* Automating scene creation for workflows
* Integrating with external tools to populate collections
# Get all scenes (/docs/api/scenes/get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve a paginated list of scenes with their metadata and associated links.
This endpoint allows you to:
* Get all scenes in your workspace
* Filter scenes by collection ID
* Paginate through large sets of scenes
* Access read-only and shared slides links for each scene
Scenes are returned with their complete metadata including creation/modification dates, pinned status, and associated sharing links.
**Use cases:**
* Building a scene browser or gallery
* Syncing scenes with external systems
* Creating scene management dashboards
* Implementing search and filter functionality
# Scenes (/docs/api/scenes)
# Create new scene (/docs/api/scenes/post)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a new empty scene with specified metadata in a collection.
This endpoint creates a scene with:
* Basic metadata (name, pinned status)
* Assignment to a specific collection
* Empty content (no drawing elements)
* Default sharing settings
This endpoint only creates the scene structure and metadata. To add content (drawings, shapes, text), use the content endpoints after creation.
The scene will be created with default permissions based on your workspace settings. Sharing links can be created separately using the links endpoints.
**Use cases:**
* Creating scenes programmatically for templates
* Bulk scene creation for projects
* Integration with external project management tools
* Automated scene setup for teams
# Get scene content (/docs/api/scenes/sceneId-content-get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve the complete content of a scene including all drawing elements and files.
This endpoint returns:
* All drawing elements (shapes, lines, text, etc.)
* Embedded files and images
* Scene settings and configuration
* Version information
* Canvas state and viewport settings
The response includes the current version of the scene. For version history or specific versions, use the versioning endpoints.
Large scenes with many elements or files may result in substantial response sizes. Consider implementing client-side caching for frequently accessed scenes.
**Use cases:**
* Loading scenes for editing
* Creating scene backups
* Exporting scene data
* Scene analysis and processing
# Patch scene content (/docs/api/scenes/sceneId-content-patch)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Patch scene content by merging supplied fields into the existing scene instead of replacing it.
Unlike the PUT endpoint which replaces all content, PATCH performs a server-side merge:
* **Elements**: Merged by element ID using version-based reconciliation. Higher version wins; equal versions are resolved by versionNonce tie-breaking. Elements not included in the request are preserved. Send elements with `isDeleted: true` to soft-delete them.
* **App State**: Shallow merge of provided fields over existing state.
* **Files**: New files are added; existing files are preserved unless replaced by a file with the same ID.
PATCH accepts a partial scene-content object. You may provide any subset of `elements`, `appState`, and `files`, but at least one of them must be present. This endpoint does not perform an authoritative replacement and does not force connected editors to reload. It writes a merged scene using the current stored content as the base. If the scene is modified concurrently, the merge result may temporarily diverge until the next editor save/reconciliation cycle. If you want to replace the entire scene with a new authoritative version, use `PUT /scenes/:sceneId/content` instead.
**Use cases:**
* Adding or updating specific elements without affecting others
* Updating background color without touching elements
* Programmatically adding images/files to an existing scene
* Building integrations that modify scenes without full content replacement
# Replace scene content (/docs/api/scenes/sceneId-content-put)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Replace the complete content of a scene with the supplied elements, files, and app state.
This endpoint allows you to:
* Replace all scene elements with new ones
* Update or add files to the scene
* Modify scene settings and app state
* Force connected editors to reload the scene after an authoritative full replacement
This is an authoritative full replacement. Any existing elements not included in the request will be removed from the scene, connected editors will be forced to reload instead of reconciling the change incrementally, and any `sceneVersion` value in the request body will be ignored because the server always recomputes it from the submitted content. If you only want to add or update specific elements, files, or app-state fields without replacing the whole scene, use `PATCH /scenes/:sceneId/content` instead.
**Use cases:**
* Restoring scene from backup
* Replacing a scene from an external system of record
* Correcting a scene even while editors are currently open
* Programmatic scene generation
# Delete scene by ID (/docs/api/scenes/sceneId-delete)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Soft-delete a scene by moving it to trash.
This action will:
* Remove the scene from active workspace lists
* Move scene metadata to trash storage
* Keep the item restorable from trash until it is permanently deleted via internal trash flows
This endpoint does not permanently delete the scene. It performs a trash move (soft delete).
Existing links or integrations may stop working while the scene is in trash.
**Use cases:**
* Moving unused or outdated scenes to trash
* Implementing scene management workflows
* Bulk soft-delete operations
* Lifecycle management before permanent deletion
# Get scene metadata (/docs/api/scenes/sceneId-get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve detailed metadata for a specific scene by its ID.
This endpoint returns:
* Complete scene metadata (name, dates, pinned status)
* All associated read-only sharing links
* All shared slides presentation links
* Collection assignment information
This endpoint only returns metadata. To get the actual scene content (drawings, shapes, etc.), use the content endpoint.
**Use cases:**
* Displaying scene information in UI
* Checking scene permissions and sharing status
* Building scene detail views
* Validating scene existence before operations
# Update scene metadata (/docs/api/scenes/sceneId-patch)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Update specific metadata fields of an existing scene.
You can update:
* Scene name
* Pinned status
* Collection assignment
* Any combination of the above fields
This endpoint uses partial updates - only provide the fields you want to change. Omitted fields will remain unchanged.
Moving a scene to a different collection may affect sharing permissions and access rights depending on collection settings.
**Use cases:**
* Renaming scenes
* Organizing scenes by pinning/unpinning
* Moving scenes between collections
* Bulk metadata updates
# Users (/docs/api/users)
# Get workspace users (/docs/api/users/workspaces-users-get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve a paginated list of users in the current workspace.
This endpoint allows you to:
* List all users in the workspace
* Paginate through large user lists
**Use cases:**
* Displaying team members in UI
* Managing workspace membership
* Auditing user access
# Delete workspace user (/docs/api/users/workspaces-users-userId-delete)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Remove a specific user from the current workspace by user ID.
Deleting a user from the workspace will revoke their access and remove all workspace-specific data.
**Use cases:**
* Removing users who no longer need access
* Managing team membership
* Enforcing security and compliance
# Get workspace user (/docs/api/users/workspaces-users-userId-get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve details for a specific user in the current workspace by user ID.
This endpoint returns all metadata for the user, excluding workspace references.
**Use cases:**
* Displaying user profiles
* Managing user roles and permissions
* Auditing user activity
# Update workspace user (/docs/api/users/workspaces-users-userId-patch)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Update the metadata of a specific user in the current workspace by user ID.
You can update:
* Name or profile picture
* Workspace role (member or admin)
* Workspace teams or preferences
Email updates are not supported through the public API for security reasons.
**Use cases:**
* Editing user profiles
* Managing user preferences
* Updating team assignments
# Workspace (/docs/api/workspace)
# Get workspace data (/docs/api/workspace/workspaces-get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve the current workspace's metadata, including users and invites.
This endpoint returns:
* Workspace name, settings, and configuration
* List of user IDs in the workspace
* Workspace invite information
**Use cases:**
* Displaying workspace details in dashboards
* Fetching workspace data for integrations
* Validating workspace context for user actions
# Get workspace invites (/docs/api/workspace/workspaces-invites-get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve a paginated list of invites for the current workspace.
This endpoint allows you to:
* List all pending and active invites
* Paginate through large invite lists
**Use cases:**
* Managing workspace invitations
* Displaying invite status in UI
* Auditing workspace access
# Delete workspace invite (/docs/api/workspace/workspaces-invites-inviteId-delete)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Delete a specific workspace invite by its ID.
Deleting an invite will prevent any further use of the invite link or email.
**Use cases:**
* Revoking unused or compromised invites
* Managing workspace access
* Cleaning up expired invites
# Get workspace invite (/docs/api/workspace/workspaces-invites-inviteId-get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve details for a specific workspace invite by its ID.
This endpoint returns all metadata for the invite, including status and restrictions.
**Use cases:**
* Displaying invite details in UI
* Validating invite status before accepting
* Auditing invite usage
# Update workspace invite (/docs/api/workspace/workspaces-invites-inviteId-patch)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Update the metadata of a specific workspace invite by its ID.
You can update:
* Email address (for email invites)
* Role assigned to the invitee
* Maximum uses or domain restrictions
**Use cases:**
* Correcting invite details
* Changing invite permissions
* Managing invite limits
# Create invite (/docs/api/workspace/workspaces-invites-post)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a new invite for the workspace.
You can create:
* Email-based invites (send to a specific email)
* Invite links (with optional restrictions and usage limits)
**Use cases:**
* Inviting new members to the workspace
* Generating invite links for onboarding
* Automating team invitations
# Update workspace data (/docs/api/workspace/workspaces-patch)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Update the current workspace's metadata.
You can update:
* Workspace name
* Workspace picture
**Use cases:**
* Renaming workspaces
* Updating branding or workspace avatars
* Managing workspace settings in admin panels
# Get workspace users (/docs/api/workspace/workspaces-users-get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve a paginated list of users in the current workspace.
This endpoint allows you to:
* List all users in the workspace
* Paginate through large user lists
**Use cases:**
* Displaying team members in UI
* Managing workspace membership
* Auditing user access
# Delete workspace user (/docs/api/workspace/workspaces-users-userId-delete)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Remove a specific user from the current workspace by user ID.
Deleting a user from the workspace will revoke their access and remove all workspace-specific data.
**Use cases:**
* Removing users who no longer need access
* Managing team membership
* Enforcing security and compliance
# Get workspace user (/docs/api/workspace/workspaces-users-userId-get)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve details for a specific user in the current workspace by user ID.
This endpoint returns all metadata for the user, excluding workspace references.
**Use cases:**
* Displaying user profiles
* Managing user roles and permissions
* Auditing user activity
# Update workspace user (/docs/api/workspace/workspaces-users-userId-patch)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Update the metadata of a specific user in the current workspace by user ID.
You can update:
* Name or profile picture
* Workspace role (member or admin)
* Workspace teams or preferences
Email updates are not supported through the public API for security reasons.
**Use cases:**
* Editing user profiles
* Managing user preferences
* Updating team assignments