Apps: canvases and lists
Making canvases, reading them as Markdown, adding and replacing sections, and reading and filling lists, with an app token.
Four scopes open canvases and lists to an app: canvases:read, canvases:write, lists:read and lists:write. The token must carry the scope and the workspace must still grant it. Everything is done as the person who authorised the app — what they may open, what they may change — and only where the app was added: a canvas or list in a channel its installation reaches (never a direct conversation, and never one only shared with that person), and the canvases the app made itself. Anything else answers 404, as if it did not exist.
Make a canvas
curl -X POST https://kemble.io/api/apps/canvases \
-H "authorization: Bearer $APP_TOKEN" \
-H 'content-type: application/json' \
-d '{ "title": "Release 4.2", "channelId": "'$CHANNEL'", "markdown": "## Release notes\n\n- Faster search\n- [ ] Tell support" }'- With channelId, the canvas is the channel’s: everyone who can open the channel can read it. Without one, it stands on its own and only the person who authorised the app can open it until they share it; add "spaceId" when they are in more than one space.
- The answer is 201 with { canvas: { id, title, spaceId, channelId, visibility, updatedAt, url } }.
Read a canvas
GET /api/apps/canvases/:canvas answers { canvas: { id, title, spaceId, channelId, archived, markdown, sections, url } }. markdown is the canvas as Markdown, headed by its title: people as @Name, a message card as a link to the message, another canvas by its title only when the app can reach it too, a file without its name. sections lists its headings — { id, level, title } — and a section is a heading and everything under it up to the next heading of the same or a higher level. A date in the canvas reads as its day in words — for example “Saturday, 14 November 2026”, or with a time “Saturday, 14 November 2026 at 15:00 (Europe/London)”.
Add or replace a section
curl -X POST https://kemble.io/api/apps/canvases/$CANVAS/sections \
-H "authorization: Bearer $APP_TOKEN" \
-H 'content-type: application/json' \
-d '{ "markdown": "## Deploys\n\nBuild 812 is out.", "position": "end" }'- "position" is "end" (the default), "start", or { "after": "<section id>" }.
- PUT /api/apps/canvases/:canvas/sections/:section with { "markdown" } replaces that section, its heading included — send the heading again to keep one. Percent-encode the id in the path (its colon as %3A) if your library does not.
- Markdown is read by Kemble’s own grammar: headings, paragraphs, bold, italic, strikethrough, code, links, bulleted, numbered and - [ ] checklists, quotes and fenced code. No HTML and no pictures.
- Somebody with the canvas open sees the section arrive at once. Before the app changes it, the canvas as it was is saved in its Version history under the app’s name — one version for a run of changes within ten minutes, and a replace made after somebody else changed the canvas saves its own — so anyone who can edit it can put it back.
- The answer is { section: { id, blocks }, canvasId, versionId }: the new section’s id when it starts with a heading, and the blocks it put in.
- The person who authorised the app must be able to edit the canvas; otherwise 403 canvas.read_only.
Lists
- GET /api/apps/lists/:list answers { list: { id, name, channelId, items, canEdit }, fields: [{ id, name, type, config }] }.
- GET /api/apps/lists/:list/items?limit=100&after=<item id> answers { items, nextAfter }, a page at a time, in the list’s order. Each item is { id, title, notes, status, assigneeId, dueAt, parentItemId, createdById, createdAt, updatedAt, completedAt, fields }, its fields keyed by field id.
- POST /api/apps/lists/:list/items with { "title", "fields": { "<field id>": value } } adds an item; PATCH /api/apps/lists/:list/items/:item changes one. status, notes, assigneeId, dueAt and parentItemId are optional. Every value is checked against its field’s type, and a wrong one is 422 with nothing written.
- An app cannot delete items, fields or lists.
Each app may make 240 reads, 60 writes and 20 canvases a minute in each workspace; past that the answer is 429 app.rate_limited with retryAfterSeconds. These limits are the app’s, never the person’s it works for.