Source
This app’s code and revision history are public. Download a copy or explore the files below.
Public
The app’s code and revision history are public.
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone 788ba0fa01083b300ad50913
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
public/openapi.json
{
"openapi": "3.1.0",
"info": {
"title": "Canvas API",
"version": "1.0.0",
"summary": "A free online whiteboard (a free Miro alternative) for people and their AI assistants.",
"description": "Start a whiteboard from a flowchart, a mind map, columns of sticky notes or a template, laid out automatically, and get back its edit, view and admin links. Read a board back as shapes, arrows and text. With the admin key, rename a board, turn its links off, make a new edit link or delete it. No account and no API key. Errors are JSON with an error sentence that says what to change. Full guide: https://canvas-free.openapps.xyz/llms.txt",
"contact": {
"name": "OpenApps",
"url": "https://openapps.free/"
}
},
"servers": [
{
"url": "https://functions.openapps.xyz/apps/788ba0fa01083b300ad50913/api"
}
],
"externalDocs": {
"url": "https://canvas-free.openapps.xyz/llms.txt",
"description": "llms.txt guide"
},
"paths": {
"/": {
"get": {
"operationId": "listRoutes",
"summary": "List the routes and the board templates",
"responses": {
"200": {
"description": "Routes and templates",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"about": {
"type": "string"
},
"docs": {
"type": "string",
"format": "uri"
},
"openapi": {
"type": "string",
"format": "uri"
},
"routes": {
"type": "array",
"items": {
"type": "string"
}
},
"templates": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"summary": {
"type": "string"
}
}
}
}
}
},
"example": {
"name": "Canvas by OpenApps",
"templates": [
{
"id": "retro",
"name": "Retro",
"summary": "What went well, what could be better, and action items."
}
]
}
}
}
}
}
}
},
"/boards": {
"post": {
"operationId": "createBoard",
"summary": "Start a board from a diagram, sticky notes or a template",
"description": "Send one of nodes (with edges), columns or template; send {} for an empty board. The board is laid out and drawn the first time someone opens one of its links. Give the person editUrl; keep adminUrl for the owner.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NewBoard"
},
"examples": {
"flowchart": {
"summary": "A flowchart",
"value": {
"title": "Checkout flow",
"nodes": [
{
"id": "cart",
"label": "Cart"
},
{
"id": "pay",
"label": "Paid?",
"shape": "diamond",
"color": "yellow"
},
{
"id": "done",
"label": "Order placed",
"shape": "ellipse",
"color": "green"
}
],
"edges": [
{
"from": "cart",
"to": "pay"
},
{
"from": "pay",
"to": "done",
"label": "Yes"
}
]
}
},
"mindmap": {
"summary": "A mind map",
"value": {
"title": "Launch plan",
"direction": "right",
"nodes": [
{
"id": "c",
"label": "Launch",
"shape": "ellipse",
"color": "yellow"
},
{
"id": "m",
"label": "Marketing",
"color": "blue"
},
{
"id": "p",
"label": "Product",
"color": "green"
}
],
"edges": [
{
"from": "c",
"to": "m"
},
{
"from": "c",
"to": "p"
}
]
}
},
"notes": {
"summary": "Sticky notes in columns",
"value": {
"title": "Sprint 12 retro",
"columns": [
{
"name": "Went well",
"color": "green",
"notes": [
"Shipped search"
]
},
{
"name": "To improve",
"color": "red",
"notes": [
"Flaky tests"
]
}
]
}
},
"template": {
"summary": "A template",
"value": {
"template": "retro"
}
}
}
}
}
},
"responses": {
"201": {
"description": "The new board and its links",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreatedBoard"
},
"example": {
"id": "Hs7kQEznzuQqAmQ3AkTp",
"title": "Checkout flow",
"viewUrl": "https://canvas-free.openapps.xyz/b/Hs7kQEznzuQqAmQ3AkTp",
"editUrl": "https://canvas-free.openapps.xyz/b/Hs7kQEznzuQqAmQ3AkTp#edit=AvPT9yPZ7gydBfHfiemamSwo",
"adminUrl": "https://canvas-free.openapps.xyz/b/Hs7kQEznzuQqAmQ3AkTp/admin#k=9P3wq7bKdNzT5mLr2xVfYs8c",
"editKey": "AvPT9yPZ7gydBfHfiemamSwo",
"adminKey": "9P3wq7bKdNzT5mLr2xVfYs8c",
"startsWith": "a diagram of 3 shapes and 2 arrows, laid out top to bottom",
"share": "Send editUrl to people who should draw on the board and viewUrl to people who should only look. Keep adminUrl private: it renames the board, turns its links off and deletes it. The board is drawn the first time someone opens editUrl."
}
}
}
},
"400": {
"description": "Something to fix, in one sentence",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "edges[0] must connect two node ids from nodes (got \"a\" to \"zzz\")."
}
}
}
}
}
}
},
"/boards/{boardId}": {
"get": {
"operationId": "readBoard",
"summary": "Read what is on a board",
"parameters": [
{
"name": "boardId",
"in": "path",
"required": true,
"description": "The part of a board link after /b/.",
"schema": {
"type": "string",
"pattern": "^[A-Za-z0-9]{16,32}$"
}
},
{
"name": "x-edit-key",
"in": "header",
"required": false,
"description": "The edit key (the part of the edit link after #edit=). Needed only when the view link is off.",
"schema": {
"type": "string"
}
},
{
"name": "x-admin-key",
"in": "header",
"required": false,
"description": "The admin key. Also accepted for reading.",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "The board and its contents",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BoardRead"
},
"example": {
"board": {
"id": "Hs7kQEznzuQqAmQ3AkTp",
"title": "Checkout flow",
"editLink": "on",
"viewLink": "on",
"viewUrl": "https://canvas-free.openapps.xyz/b/Hs7kQEznzuQqAmQ3AkTp"
},
"contents": {
"frames": [],
"shapes": [
{
"id": "s-n0",
"shape": "rectangle",
"text": "Cart",
"color": "white",
"x": 40,
"y": 0,
"width": 150,
"height": 70
},
{
"id": "s-n1",
"shape": "diamond",
"text": "Paid?",
"color": "yellow",
"x": 20,
"y": 150,
"width": 240,
"height": 112
}
],
"arrows": [
{
"id": "s-a0",
"from": "Cart",
"to": "Paid?"
}
],
"texts": [],
"drawings": 0,
"images": 0,
"elements": 5
}
}
}
}
},
"403": {
"description": "Something to fix, in one sentence",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "This board's view link is off. Send its admin key in x-admin-key, or its edit key in x-edit-key while the edit link is on."
}
}
}
},
"404": {
"description": "Something to fix, in one sentence",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "No board with that id. The id is the part of the link after /b/."
}
}
}
}
}
},
"patch": {
"operationId": "changeBoard",
"summary": "Rename a board, turn its links on or off, or make a new edit link",
"parameters": [
{
"name": "boardId",
"in": "path",
"required": true,
"description": "The part of a board link after /b/.",
"schema": {
"type": "string",
"pattern": "^[A-Za-z0-9]{16,32}$"
}
},
{
"name": "x-admin-key",
"in": "header",
"required": true,
"description": "The admin key: the part of the admin link after #k=.",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BoardChange"
},
"example": {
"title": "Checkout v2",
"viewLink": false,
"newEditLink": true
}
}
}
},
"responses": {
"200": {
"description": "The board with its current links",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"board": {
"$ref": "#/components/schemas/BoardInfo"
}
}
},
"example": {
"board": {
"id": "Hs7kQEznzuQqAmQ3AkTp",
"title": "Checkout v2",
"editLink": "on",
"viewLink": "off",
"viewUrl": "https://canvas-free.openapps.xyz/b/Hs7kQEznzuQqAmQ3AkTp",
"editUrl": "https://canvas-free.openapps.xyz/b/Hs7kQEznzuQqAmQ3AkTp#edit=AvPT9yPZ7gydBfHfiemamSwo",
"adminUrl": "https://canvas-free.openapps.xyz/b/Hs7kQEznzuQqAmQ3AkTp/admin#k=9P3wq7bKdNzT5mLr2xVfYs8c"
}
}
}
}
},
"400": {
"description": "Something to fix, in one sentence",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "Send title, editLink, viewLink or newEditLink."
}
}
}
},
"401": {
"description": "Something to fix, in one sentence",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "Changing a board needs its admin key in the x-admin-key header."
}
}
}
},
"403": {
"description": "Something to fix, in one sentence",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That admin key does not match this board. It is the part of the admin link after #k=."
}
}
}
},
"404": {
"description": "Something to fix, in one sentence",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "No board with that id. The id is the part of the link after /b/."
}
}
}
}
}
},
"delete": {
"operationId": "deleteBoard",
"summary": "Delete a board and everything on it",
"parameters": [
{
"name": "boardId",
"in": "path",
"required": true,
"description": "The part of a board link after /b/.",
"schema": {
"type": "string",
"pattern": "^[A-Za-z0-9]{16,32}$"
}
},
{
"name": "x-admin-key",
"in": "header",
"required": true,
"description": "The admin key: the part of the admin link after #k=.",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Deleted",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"deleted": {
"type": "boolean"
},
"id": {
"type": "string"
},
"note": {
"type": "string"
}
}
},
"example": {
"deleted": true,
"id": "Hs7kQEznzuQqAmQ3AkTp",
"note": "The board and everything on it are gone. Its links no longer open anything."
}
}
}
},
"401": {
"description": "Something to fix, in one sentence",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "Deleting a board needs its admin key in the x-admin-key header."
}
}
}
},
"403": {
"description": "Something to fix, in one sentence",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "That admin key does not match this board. It is the part of the admin link after #k=."
}
}
}
},
"404": {
"description": "Something to fix, in one sentence",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
},
"example": {
"error": "No board with that id. The id is the part of the link after /b/."
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"Error": {
"type": "object",
"required": [
"error"
],
"properties": {
"error": {
"type": "string",
"description": "A sentence that says what to change."
}
}
},
"Node": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"type": "string",
"maxLength": 80
},
"label": {
"type": "string",
"maxLength": 200,
"description": "Defaults to the id."
},
"shape": {
"type": "string",
"enum": [
"rectangle",
"ellipse",
"diamond"
],
"default": "rectangle"
},
"color": {
"type": "string",
"enum": [
"yellow",
"green",
"blue",
"red",
"violet",
"orange",
"gray",
"white"
],
"default": "white"
}
}
},
"Edge": {
"type": "object",
"required": [
"from",
"to"
],
"properties": {
"from": {
"type": "string",
"description": "A node id."
},
"to": {
"type": "string",
"description": "A node id."
},
"label": {
"type": "string",
"maxLength": 200
}
}
},
"Column": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 200
},
"color": {
"type": "string",
"enum": [
"yellow",
"green",
"blue",
"red",
"violet",
"orange",
"gray",
"white"
],
"default": "yellow"
},
"notes": {
"type": "array",
"maxItems": 60,
"items": {
"type": "string",
"maxLength": 300
},
"description": "An empty string is an empty note."
}
}
},
"NewBoard": {
"type": "object",
"properties": {
"title": {
"type": "string",
"maxLength": 120,
"description": "Defaults to the template's name, or Untitled board."
},
"heading": {
"type": "string",
"maxLength": 200,
"description": "Big text above a diagram or columns."
},
"nodes": {
"type": "array",
"maxItems": 150,
"items": {
"$ref": "#/components/schemas/Node"
}
},
"edges": {
"type": "array",
"maxItems": 300,
"items": {
"$ref": "#/components/schemas/Edge"
}
},
"direction": {
"type": "string",
"enum": [
"down",
"right"
],
"default": "down",
"description": "Use right for mind maps."
},
"columns": {
"type": "array",
"maxItems": 12,
"items": {
"$ref": "#/components/schemas/Column"
}
},
"template": {
"type": "string",
"enum": [
"brainstorm",
"retro",
"user-flow",
"weekly-plan"
]
}
}
},
"CreatedBoard": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"title": {
"type": "string"
},
"viewUrl": {
"type": "string",
"format": "uri"
},
"editUrl": {
"type": "string",
"format": "uri"
},
"adminUrl": {
"type": "string",
"format": "uri"
},
"editKey": {
"type": "string"
},
"adminKey": {
"type": "string"
},
"startsWith": {
"type": "string"
},
"share": {
"type": "string"
}
}
},
"BoardInfo": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"title": {
"type": "string"
},
"editLink": {
"type": "string",
"enum": [
"on",
"off"
]
},
"viewLink": {
"type": "string",
"enum": [
"on",
"off"
]
},
"viewUrl": {
"type": "string",
"format": "uri"
},
"editUrl": {
"type": "string",
"format": "uri",
"description": "Only when your key allows it."
},
"adminUrl": {
"type": "string",
"format": "uri",
"description": "Only with the admin key."
}
}
},
"BoardRead": {
"type": "object",
"properties": {
"board": {
"$ref": "#/components/schemas/BoardInfo"
},
"contents": {
"type": [
"object",
"null"
],
"description": "Null until the board is first opened.",
"properties": {
"frames": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"items": {
"type": "integer"
}
}
}
},
"shapes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"shape": {
"type": "string"
},
"text": {
"type": "string"
},
"color": {
"type": "string"
},
"frame": {
"type": "string"
},
"x": {
"type": "number"
},
"y": {
"type": "number"
},
"width": {
"type": "number"
},
"height": {
"type": "number"
}
}
}
},
"arrows": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"from": {
"type": "string"
},
"to": {
"type": "string"
},
"text": {
"type": "string"
}
}
}
},
"texts": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"text": {
"type": "string"
},
"x": {
"type": "number"
},
"y": {
"type": "number"
}
}
}
},
"drawings": {
"type": "integer"
},
"images": {
"type": "integer"
},
"elements": {
"type": "integer"
}
}
},
"startsWith": {
"type": "string"
},
"note": {
"type": "string",
"description": "Says why contents is null, or that contents covers only the first 5000 elements of a very large board."
}
}
},
"BoardChange": {
"type": "object",
"properties": {
"title": {
"type": "string",
"maxLength": 120
},
"editLink": {
"type": "boolean"
},
"viewLink": {
"type": "boolean"
},
"newEditLink": {
"type": "boolean",
"const": true
}
}
}
}
}
}
Change history
Point OpenApps links to openapps.free
Bind AGENT_ADDRESS in policy constants
Use functions.openapps.xyz alias, add OpenApp contribution strip
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync