Developers
Whiteboards, presentations, search and storage API
This page covers the rest of the REST API: whiteboards and their elements, Studio presentations and their slides, search across a whole workspace, and My drive, where you can list, download, upload and delete files. As everywhere in the API, each request acts as you and only reaches what you can see in the app.
Before you start
- Base URL:
https://app.tylode.com/api/v1 - Send your token in the
Authorization: Bearer tylode_pat_xxxheader. Personal access tokens need the Pro plan. See Authentication. - Get your workspace ID with
GET /api/v1/workspaces, or in the app under Settings → Workspace → Workspace details → Workspace ID → Copy. - Errors any endpoint can return (
401,403,429) are covered in Errors and conventions. - The JavaScript examples use the
tylode()helper from the Pages API.
Endpoints
| Method | Path | What it does | Scope |
|---|---|---|---|
| GET | /api/v1/whiteboards | List a workspace's whiteboards | whiteboards:read |
| POST | /api/v1/whiteboards | Create a whiteboard | whiteboards:write |
| GET | /api/v1/whiteboards/:id/elements | List a whiteboard's elements | whiteboards:read |
| POST | /api/v1/whiteboards/:id/elements | Add an element | whiteboards:write |
| PATCH | /api/v1/whiteboards/:id/elements/:elementId | Move or change an element | whiteboards:write |
| DELETE | /api/v1/whiteboards/:id/elements/:elementId | Delete an element | whiteboards:delete |
| DELETE | /api/v1/whiteboards/:id | Move a whiteboard to the trash | whiteboards:delete |
| GET | /api/v1/presentations | List your presentations | presentations:read |
| POST | /api/v1/presentations | Create a presentation | presentations:write |
| GET | /api/v1/presentations/:id/slides | List a presentation's slides | presentations:read |
| POST | /api/v1/presentations/:id/slides | Add a blank slide | presentations:write |
| GET | /api/v1/presentations/:id/slides/:slideId/elements | List a slide's elements | presentations:read |
| POST | /api/v1/presentations/:id/slides/:slideId/elements | Add text or a shape to a slide | presentations:write |
| PATCH | /api/v1/presentations/:id/slides/:slideId/elements/:elementId | Move or change a slide element | presentations:write |
| DELETE | /api/v1/presentations/:id/slides/:slideId/elements/:elementId | Delete a slide element | presentations:delete |
| DELETE | /api/v1/presentations/:id/slides/:slideId | Delete a slide | presentations:delete |
| DELETE | /api/v1/presentations/:id | Move a presentation to the trash | presentations:delete |
| GET | /api/v1/search | Search a workspace | search:read |
| GET | /api/v1/storage | List a folder of My drive | storage:read |
| GET | /api/v1/storage/:id | Get a file with a download link | storage:read |
| POST | /api/v1/storage | Upload a file to My drive | storage:write |
| DELETE | /api/v1/storage/:id | Move a My drive file to the trash | storage:delete |
Scopes only matter for OAuth apps.
Whiteboards
Whiteboards belong to a workspace. See Whiteboards for how they work in the app.
List whiteboards
GET /api/v1/whiteboards
Returns the whiteboards in a workspace, oldest first, with id, workspace_id, name and created_by. Whiteboards in the trash aren't included.
Scope: whiteboards:read
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id | query | string | Yes | The workspace to list. |
curl "https://app.tylode.com/api/v1/whiteboards?workspace_id=WORKSPACE_ID" \
-H "Authorization: Bearer tylode_pat_xxx"Response 200
{
"data": [
{
"id": "b2e5a8d1-4c7f-4a0b-9e3d-6f9c2b5e8a14",
"workspace_id": "7c1e2a4b-3f5d-4e8a-9b21-6d0f5c8e1a37",
"name": "Retro Q3",
"created_by": "2f7c9e41-8b3a-4d6e-a1f5-0c8e3b7d9a52"
}
]
}Errors
| Status | When |
|---|---|
400 | workspace_id is missing. |
Create a whiteboard
POST /api/v1/whiteboards
Creates an empty whiteboard.
Scope: whiteboards:write
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id | body | string | Yes | Workspace to create it in. |
name | body | string | No | Name. Defaults to Whiteboard. |
curl -X POST https://app.tylode.com/api/v1/whiteboards \
-H "Authorization: Bearer tylode_pat_xxx" \
-H "Content-Type: application/json" \
-d '{ "workspace_id": "WORKSPACE_ID", "name": "Brainstorm: pricing page" }'Response 201
{
"data": {
"id": "c3f6b9e2-5d8a-4b1c-a4e7-0a3d6c9f2b25",
"workspace_id": "7c1e2a4b-3f5d-4e8a-9b21-6d0f5c8e1a37",
"name": "Brainstorm: pricing page",
"created_by": "2f7c9e41-8b3a-4d6e-a1f5-0c8e3b7d9a52"
}
}Errors
| Status | When |
|---|---|
400 | workspace_id is missing, or the whiteboard couldn't be created (for example, you can't create content in that workspace). |
List elements
GET /api/v1/whiteboards/:id/elements
Returns every element on a whiteboard, from back to front (by z_index).
Scope: whiteboards:read
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Whiteboard ID. |
Each element has id, whiteboard_id, type, x, y, width, height, content, style and z_index. The list includes every type on the board: sticky, text, rectangle, ellipse, and also connector, embed and media, which you can read but not create through the API. It doesn't say which elements a connector joins.
curl https://app.tylode.com/api/v1/whiteboards/WHITEBOARD_ID/elements \
-H "Authorization: Bearer tylode_pat_xxx"Response 200
{
"data": [
{
"id": "d4a7c0f3-6e9b-4c2d-b5f8-1b4e7d0a3c36",
"whiteboard_id": "b2e5a8d1-4c7f-4a0b-9e3d-6f9c2b5e8a14",
"type": "sticky",
"x": 120,
"y": 80,
"width": 180,
"height": 120,
"content": "Too many clicks to invite someone",
"style": { "color": "#fecaca" },
"z_index": 3
}
]
}A whiteboard that doesn't exist, or that you can't see, returns an empty list.
Add an element
POST /api/v1/whiteboards/:id/elements
Adds a sticky note, a text or a shape to a whiteboard.
Scope: whiteboards:write
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Whiteboard ID. |
type | body | string | Yes | sticky, text, rectangle or ellipse. |
x | body | number | No | Horizontal position of the element's top-left corner on the canvas. Defaults to 0. |
y | body | number | No | Vertical position of the top-left corner. Defaults to 0. |
width | body | number | No | Width. Defaults to 200. |
height | body | number | No | Height. Defaults to 200. |
content | body | string | No | Text shown inside. Defaults to empty. |
style | body | object | No | Appearance. Defaults to {}. |
Coordinates are in canvas units (pixels at 100% zoom). (0, 0) is the canvas origin and y grows downwards, so to lay out a row of notes, keep y and add the width plus a gap to x.
The style key that the whiteboard uses for these types is color, a CSS color such as "#bbf7d0":
| Type | What style.color does | Without it |
|---|---|---|
sticky | Note color. The app's palette is #fde68a, #bbf7d0, #bfdbfe, #fbcfe8, #fecaca and #ddd6fe. | Yellow (#fde68a). |
rectangle, ellipse | Fill color. | A light, see-through fill with a border. |
text | Nothing. | — |
New elements are created with z_index 0, so if they overlap existing elements they may appear behind them.
curl -X POST https://app.tylode.com/api/v1/whiteboards/WHITEBOARD_ID/elements \
-H "Authorization: Bearer tylode_pat_xxx" \
-H "Content-Type: application/json" \
-d '{
"type": "sticky",
"x": 320,
"y": 80,
"width": 180,
"height": 120,
"content": "Add a template gallery",
"style": { "color": "#bbf7d0" }
}'// One sticky note per idea, in a row
const ideas = ["Shorter signup", "Import from Trello", "Dark mode for boards"];
for (const [i, idea] of ideas.entries()) {
await tylode(`/whiteboards/${WHITEBOARD_ID}/elements`, {
method: "POST",
body: { type: "sticky", x: i * 200, y: 0, width: 180, height: 120, content: idea },
});
}Response 201
{
"data": {
"id": "e5b8d1a4-7f0c-4d3e-a6b9-2c5f8e1b4d47",
"whiteboard_id": "b2e5a8d1-4c7f-4a0b-9e3d-6f9c2b5e8a14",
"type": "sticky",
"x": 320,
"y": 80,
"width": 180,
"height": 120,
"content": "Add a template gallery",
"style": { "color": "#bbf7d0" },
"z_index": 0
}
}Errors
| Status | When |
|---|---|
400 | type is missing or isn't one of the four allowed types. |
400 | The element couldn't be created (for example, the whiteboard doesn't exist or you can't edit it). |
Change an element
PATCH /api/v1/whiteboards/:id/elements/:elementId
Moves an element or changes its size, text or style. Only what you send changes; the rest of the element stays as it is.
Scope: whiteboards:write
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Whiteboard ID. |
elementId | path | string | Yes | Element ID, from List elements. |
x | body | number | No | New horizontal position of the top-left corner. |
y | body | number | No | New vertical position. |
width | body | number | No | New width, greater than 0. |
height | body | number | No | New height, greater than 0. |
content | body | string | No | New text. Only on notes, text boxes and shapes: on anything else (images, videos, connectors, embeds…) you get 400 with code: "content_not_editable", though you can still move and resize them. |
style | body | object | No | The style keys that change. They're merged with the style it already had. |
Send at least one of the body fields. Numbers are in canvas units and can't go beyond 100,000 (positive or negative).
style accepts these keys: color, fill, stroke, fontSize, fontFamily, bold, italic, underline, textAlign, lineHeight, letterSpacing and opacity. Each takes a string (up to 100 characters), a number or a boolean; other keys are ignored. Only the keys you send change: { "style": { "color": "#fecaca" } } changes the color and leaves the rest of the style as it was.
curl -X PATCH https://app.tylode.com/api/v1/whiteboards/WHITEBOARD_ID/elements/ELEMENT_ID \
-H "Authorization: Bearer tylode_pat_xxx" \
-H "Content-Type: application/json" \
-d '{
"x": 520,
"content": "Add a template gallery (v2)",
"style": { "color": "#fecaca" }
}'Response 200
{
"data": {
"id": "e5b8d1a4-7f0c-4d3e-a6b9-2c5f8e1b4d47",
"whiteboard_id": "b2e5a8d1-4c7f-4a0b-9e3d-6f9c2b5e8a14",
"type": "sticky",
"x": 520,
"y": 80,
"width": 180,
"height": 120,
"content": "Add a template gallery (v2)",
"style": { "color": "#fecaca" },
"z_index": 0
}
}Errors
| Status | When |
|---|---|
400 | The whiteboard or element ID isn't valid. |
400 | You didn't send anything to change. |
400 | x, y, width or height isn't a valid number (width and height have to be greater than 0), or content isn't text. |
400 | style isn't an object. |
404 | The element doesn't exist, isn't on that whiteboard, or you can't change it. |
Delete an element
DELETE /api/v1/whiteboards/:id/elements/:elementId
Deletes an element from a whiteboard. Elements have no trash: it can't be recovered.
Scope: whiteboards:delete
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Whiteboard ID. |
elementId | path | string | Yes | Element ID. |
curl -X DELETE https://app.tylode.com/api/v1/whiteboards/WHITEBOARD_ID/elements/ELEMENT_ID \
-H "Authorization: Bearer tylode_pat_xxx"Response 200
{ "data": { "id": "e5b8d1a4-7f0c-4d3e-a6b9-2c5f8e1b4d47", "papelera": false } }Errors
| Status | When |
|---|---|
400 | The whiteboard or element ID isn't valid. |
404 | The element doesn't exist, isn't on that whiteboard, or you can't delete it. |
Move a whiteboard to the trash
DELETE /api/v1/whiteboards/:id
Moves a whiteboard to the trash, the same as deleting it in the app. It can be recovered from the trash, and while it's there it doesn't appear in List whiteboards.
Scope: whiteboards:delete
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Whiteboard ID. |
curl -X DELETE https://app.tylode.com/api/v1/whiteboards/WHITEBOARD_ID \
-H "Authorization: Bearer tylode_pat_xxx"Response 200
{ "data": { "id": "b2e5a8d1-4c7f-4a0b-9e3d-6f9c2b5e8a14", "papelera": true } }Errors
| Status | When |
|---|---|
400 | The ID isn't valid. |
403 | You can see the whiteboard, but your role doesn't let you move it to the trash. |
404 | The whiteboard doesn't exist, is already in the trash, or you can't see it. |
What all DELETE endpoints have in common is in Deleting: trash or permanent.
Presentations
Presentations are Studio documents. They're personal: they belong to you, not to a workspace, and live in My drive. The list includes the ones you own and the ones shared with you. See Studio.
These endpoints don't need a workspace_id; if you send one, it's ignored.
List presentations
GET /api/v1/presentations
Returns your Studio documents and the ones shared with you, oldest first. Documents in the trash are left out.
Scope: presentations:read
Each item has id, owner_id, folder_id (the My drive folder it's in, or null), name, created_by, canvas_width and canvas_height. Every Studio format (presentations, documents, social posts, print) comes through here; the canvas size in pixels tells them apart, for example 960 × 540 for a 16:9 presentation.
curl https://app.tylode.com/api/v1/presentations \
-H "Authorization: Bearer tylode_pat_xxx"Response 200
{
"data": [
{
"id": "f6c9e2b5-8a1d-4e4f-b7c0-3d6a9f2c5e58",
"owner_id": "2f7c9e41-8b3a-4d6e-a1f5-0c8e3b7d9a52",
"folder_id": null,
"name": "Q3 results",
"created_by": "2f7c9e41-8b3a-4d6e-a1f5-0c8e3b7d9a52",
"canvas_width": 960,
"canvas_height": 540
}
]
}Create a presentation
POST /api/v1/presentations
Creates a presentation with one blank slide, just like creating one in Studio. The canvas is 960 × 540 (16:9).
Scope: presentations:write
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
name | body | string | No | Name. Defaults to Presentación. |
folder_id | body | string | No | My drive folder to put it in. Get folder IDs from List a folder. Defaults to the root. |
curl -X POST https://app.tylode.com/api/v1/presentations \
-H "Authorization: Bearer tylode_pat_xxx" \
-H "Content-Type: application/json" \
-d '{ "name": "Kick-off deck" }'Response 201
{
"data": {
"id": "a7d0f3c6-9b2e-4f5a-a8d1-4e7b0a3d6f69",
"owner_id": "2f7c9e41-8b3a-4d6e-a1f5-0c8e3b7d9a52",
"folder_id": null,
"name": "Kick-off deck",
"created_by": "2f7c9e41-8b3a-4d6e-a1f5-0c8e3b7d9a52",
"canvas_width": 960,
"canvas_height": 540
}
}Errors
| Status | When |
|---|---|
400 | The presentation couldn't be created (for example, the folder_id doesn't exist). |
List slides
GET /api/v1/presentations/:id/slides
Returns the slides of a presentation in order, with id, presentation_id, position (0 is the first) and background. The content of each slide is in List a slide's elements.
Scope: presentations:read
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Presentation ID. |
curl https://app.tylode.com/api/v1/presentations/PRESENTATION_ID/slides \
-H "Authorization: Bearer tylode_pat_xxx"Response 200
{
"data": [
{
"id": "b8e1a4d7-0c3f-4a6b-9e2d-5f8c1b4e7a70",
"presentation_id": "a7d0f3c6-9b2e-4f5a-a8d1-4e7b0a3d6f69",
"position": 0,
"background": { "color": "#ffffff" }
}
]
}A presentation you can't see returns an empty list.
Add a slide
POST /api/v1/presentations/:id/slides
Adds a blank slide at the end of a presentation. It takes no body.
Scope: presentations:write
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Presentation ID. |
curl -X POST https://app.tylode.com/api/v1/presentations/PRESENTATION_ID/slides \
-H "Authorization: Bearer tylode_pat_xxx"Response 201
{
"data": {
"id": "c9f2b5e8-1d4a-4b7c-a0e3-6a9d2c5f8b81",
"presentation_id": "a7d0f3c6-9b2e-4f5a-a8d1-4e7b0a3d6f69",
"position": 1,
"background": { "color": "#ffffff" }
}
}Errors
| Status | When |
|---|---|
400 | The slide couldn't be added (for example, the presentation doesn't exist or you can't edit it). |
List a slide's elements
GET /api/v1/presentations/:id/slides/:slideId/elements
Returns the elements of a slide, back to front (by z_index), with id, slide_id, type, x, y, width, height, content, style and z_index. It includes every type on the slide (text, shapes, images…), although through the API you can only add text, rectangles and ellipses.
Scope: presentations:read
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Presentation ID. |
slideId | path | string | Yes | Slide ID, from List slides. |
Coordinates are in pixels of the presentation's canvas (canvas_width × canvas_height, 960 × 540 for the ones the API creates), with (0, 0) at the top-left corner and y growing downwards.
curl https://app.tylode.com/api/v1/presentations/PRESENTATION_ID/slides/SLIDE_ID/elements \
-H "Authorization: Bearer tylode_pat_xxx"Response 200
{
"data": [
{
"id": "d0a3c6f9-2e5b-4c8d-b1e4-7f0a3d6c9b92",
"slide_id": "b8e1a4d7-0c3f-4a6b-9e2d-5f8c1b4e7a70",
"type": "text",
"x": 80,
"y": 60,
"width": 800,
"height": 80,
"content": "Q3 results",
"style": { "fontSize": 48, "bold": true },
"z_index": 0
}
]
}Errors
| Status | When |
|---|---|
400 | The presentation or slide ID isn't valid. |
404 | The slide doesn't exist, isn't in that presentation, or you can't see it. |
Add an element to a slide
POST /api/v1/presentations/:id/slides/:slideId/elements
Adds a text box, a rectangle or an ellipse to a slide, in front of everything already on it.
Scope: presentations:write
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Presentation ID. |
slideId | path | string | Yes | Slide ID. |
type | body | string | No | text, rectangle or ellipse. Defaults to text. |
x | body | number | No | Horizontal position of the top-left corner. Defaults to 80. |
y | body | number | No | Vertical position of the top-left corner. Defaults to 80. |
width | body | number | No | Width. Defaults to 600 for text and 300 for shapes. |
height | body | number | No | Height. Defaults to 80 for text and 200 for shapes. |
content | body | string | No | Text shown. Defaults to empty. |
style | body | object | No | Appearance, with the same keys as in Change an element. Text gets fontSize 32 unless you send another. |
curl -X POST https://app.tylode.com/api/v1/presentations/PRESENTATION_ID/slides/SLIDE_ID/elements \
-H "Authorization: Bearer tylode_pat_xxx" \
-H "Content-Type: application/json" \
-d '{
"type": "text",
"content": "Q3 results",
"x": 80,
"y": 60,
"width": 800,
"height": 80,
"style": { "fontSize": 48, "bold": true }
}'// A title and, below it, a colored box
const path = `/presentations/${PRESENTATION_ID}/slides/${SLIDE_ID}/elements`;
await tylode(path, {
method: "POST",
body: { content: "Q3 results", x: 80, y: 60, width: 800, height: 80, style: { fontSize: 48, bold: true } },
});
await tylode(path, {
method: "POST",
body: { type: "rectangle", x: 80, y: 180, width: 800, height: 280, style: { fill: "#e0f2fe" } },
});Response 201
{
"data": {
"id": "d0a3c6f9-2e5b-4c8d-b1e4-7f0a3d6c9b92",
"slide_id": "b8e1a4d7-0c3f-4a6b-9e2d-5f8c1b4e7a70",
"type": "text",
"x": 80,
"y": 60,
"width": 800,
"height": 80,
"content": "Q3 results",
"style": { "fontSize": 48, "bold": true },
"z_index": 0
}
}Errors
| Status | When |
|---|---|
400 | The presentation or slide ID isn't valid. |
400 | type isn't text, rectangle or ellipse. |
400 | x, y, width or height isn't a valid number, or content isn't text. |
400 | style isn't an object. |
400 | The element couldn't be added (for example, because you can't edit the presentation). |
404 | The slide doesn't exist, isn't in that presentation, or you can't see it. |
Change a slide element
PATCH /api/v1/presentations/:id/slides/:slideId/elements/:elementId
Works like Change an element on a whiteboard: it moves an element or changes its size, text or style, and only what you send changes. The style is merged with the one it already had.
Scope: presentations:write
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Presentation ID. |
slideId | path | string | Yes | Slide ID. |
elementId | path | string | Yes | Element ID, from List a slide's elements. |
x, y | body | number | No | New position. |
width, height | body | number | No | New size, greater than 0. |
content | body | string | No | New text. Only on notes, text boxes and shapes: on anything else (images, videos, connectors, embeds…) you get 400 with code: "content_not_editable", though you can still move and resize them. |
style | body | object | No | The style keys that change. |
curl -X PATCH https://app.tylode.com/api/v1/presentations/PRESENTATION_ID/slides/SLIDE_ID/elements/ELEMENT_ID \
-H "Authorization: Bearer tylode_pat_xxx" \
-H "Content-Type: application/json" \
-d '{ "content": "Q3 results: +18%", "style": { "color": "#0f766e" } }'Response 200
The element as it ends up, with the same fields as when you add it.
Errors
| Status | When |
|---|---|
400 | One of the IDs isn't valid. |
400 | You didn't send anything to change, a value isn't valid, or style isn't an object. |
404 | The slide or the element doesn't exist, isn't in that presentation, or you can't change it. |
Delete a slide element
DELETE /api/v1/presentations/:id/slides/:slideId/elements/:elementId
Deletes a text box, shape or image from a slide. It can't be recovered.
Scope: presentations:delete
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Presentation ID. |
slideId | path | string | Yes | Slide ID. |
elementId | path | string | Yes | Element ID. |
curl -X DELETE https://app.tylode.com/api/v1/presentations/PRESENTATION_ID/slides/SLIDE_ID/elements/ELEMENT_ID \
-H "Authorization: Bearer tylode_pat_xxx"Response 200
{ "data": { "id": "d0a3c6f9-2e5b-4c8d-b1e4-7f0a3d6c9b92", "papelera": false } }Errors
| Status | When |
|---|---|
400 | One of the IDs isn't valid. |
404 | The slide or the element doesn't exist, isn't in that presentation, or you can't delete it. |
Delete a slide
DELETE /api/v1/presentations/:id/slides/:slideId
Deletes a slide with all its elements. Slides have no trash: it can't be recovered.
Scope: presentations:delete
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Presentation ID. |
slideId | path | string | Yes | Slide ID. |
curl -X DELETE https://app.tylode.com/api/v1/presentations/PRESENTATION_ID/slides/SLIDE_ID \
-H "Authorization: Bearer tylode_pat_xxx"Response 200
{ "data": { "id": "c9f2b5e8-1d4a-4b7c-a0e3-6a9d2c5f8b81", "papelera": false } }Errors
| Status | When |
|---|---|
400 | The presentation or slide ID isn't valid. |
404 | The slide doesn't exist, isn't in that presentation, or you can't delete it. |
Move a presentation to the trash
DELETE /api/v1/presentations/:id
Moves a presentation to the My drive trash, the same as deleting it in Studio. It can be recovered, and while it's in the trash it doesn't appear in List presentations.
Scope: presentations:delete
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Presentation ID. |
curl -X DELETE https://app.tylode.com/api/v1/presentations/PRESENTATION_ID \
-H "Authorization: Bearer tylode_pat_xxx"Response 200
{ "data": { "id": "a7d0f3c6-9b2e-4f5a-a8d1-4e7b0a3d6f69", "papelera": true } }Errors
| Status | When |
|---|---|
400 | The ID isn't valid. |
404 | The presentation doesn't exist, is already in the trash, or you can't delete it. |
What all DELETE endpoints have in common is in Deleting: trash or permanent.
Search
One call searches pages, boards, whiteboards, presentations, mail and chat messages, ranked by relevance. It's the best starting point for "find the page about X" in an integration or an AI agent. See Search for search in the app.
Search a workspace
GET /api/v1/search
Scope: search:read
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id | query | string | Yes | The workspace to search. |
q | query | string | Yes | What to look for. Can't be blank. |
limit | query | number | No | Maximum results, from 1 to 50. Defaults to 20. Values outside the range are clamped. |
Each result has kind, id, title, snippet, parent_id and rank. What's searched and how it's ranked depends on the kind:
kind | Matches when | title | snippet | rank |
|---|---|---|---|---|
page | The page text matches q as a full-text search, or the title contains q. Pages in the trash are skipped. | Page title | First 140 characters of the page text | 1 or more, higher for better text matches |
board | The name contains q. | Board name | null | 0.9 |
whiteboard | The name contains q. | Whiteboard name | null | 0.9 |
presentation | The name contains q. Your presentations are found from any workspace. | Presentation name | null | 0.9 |
mail | The subject of a thread you're in contains q. | Subject | null | 0.8 |
message | The message text matches q as a full-text search, in a conversation you're in. | Conversation name (Chat for direct messages) | First 140 characters of the message | Text relevance, usually well below 1 |
- "Contains" is case-insensitive.
- Full-text search matches whole words and applies Spanish language rules, so Spanish plurals and verb forms match their root. You can use
"quoted phrases",ORbetween words, and-wordto exclude one. - For
messageresults,parent_idis the conversation ID; it'snullfor everything else. - Results are sorted by
rank(highest first) and then by title, so pages always come before other kinds.
curl -G https://app.tylode.com/api/v1/search \
-H "Authorization: Bearer tylode_pat_xxx" \
--data-urlencode "workspace_id=WORKSPACE_ID" \
--data-urlencode "q=presupuesto" \
--data-urlencode "limit=5"const params = new URLSearchParams({ workspace_id: WORKSPACE_ID, q: "presupuesto", limit: "5" });
const results = await tylode(`/search?${params}`);
const firstPage = results.find((r) => r.kind === "page");Response 200
{
"data": [
{
"kind": "page",
"id": "0e3a6d9c-2f5b-4c8e-b1a4-7d0f3b6e9c92",
"title": "Presupuesto 2027",
"snippet": "Resumen del presupuesto por equipos. Marketing sube un 12 % respecto a 2026 por la campaña de lanzamiento…",
"parent_id": null,
"rank": 1.0759909
},
{
"kind": "board",
"id": "1f4b7e0a-3c6d-4d9f-a2b5-8e1c4f7a0d03",
"title": "Presupuesto: aprobaciones",
"snippet": null,
"parent_id": null,
"rank": 0.9
},
{
"kind": "message",
"id": "2a5c8f1b-4d7e-4e0a-b3c6-9f2d5a8b1e14",
"title": "Finance",
"snippet": "He subido el presupuesto revisado, echadle un ojo antes del jueves",
"parent_id": "3c8a1e6f-9d2b-4f5a-a7e4-1b6c9d3f0e25",
"rank": 0.0607927
}
]
}Errors
| Status | When |
|---|---|
400 | workspace_id or q is missing. |
Databases, calendar events, cards and files in My drive aren't included in search results.
Storage (My drive)
My drive is your personal file storage. The API can list folders and files, download a file, upload new files and move them to the trash. There's no endpoint to rename or move files; do that in the app. See My drive.
My drive belongs to you, not to a workspace, so reading it doesn't need a workspace_id (it's ignored if you send one). Uploading does: see Upload a file. Files in the trash are left out.
List a folder
GET /api/v1/storage
Returns the folders and files directly inside a folder of your drive, each group ordered by position.
Scope: storage:read
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
folder_id | query | string | No | Folder to list. Leave it out for the root of My drive. |
The response has two lists: folders (id, parent_folder_id, name, position) and files (id, folder_id, attachment_id, position, filename, mime_type, size_bytes). To walk the whole drive, call it again with each folder's id.
curl "https://app.tylode.com/api/v1/storage?folder_id=FOLDER_ID" \
-H "Authorization: Bearer tylode_pat_xxx"Response 200
{
"data": {
"folders": [
{
"id": "3b6d9f2c-5e8a-4a1b-b4d7-0a3e6c9f2b25",
"parent_folder_id": "4c7e0a3d-6f9b-4b2c-a5e8-1b4f7d0a3c36",
"name": "Invoices",
"position": 0
}
],
"files": [
{
"id": "5d8f1b4e-7a0c-4c3d-b6f9-2c5a8e1b4d47",
"folder_id": "4c7e0a3d-6f9b-4b2c-a5e8-1b4f7d0a3c36",
"attachment_id": "6e9a2c5f-8b1d-4d4e-a7a0-3d6b9f2c5e58",
"position": 0,
"filename": "contract-northwind.pdf",
"mime_type": "application/pdf",
"size_bytes": 248193
}
]
}
}A folder that doesn't exist, or isn't yours, returns two empty lists.
Get a file
GET /api/v1/storage/:id
Returns a file's details and a temporary download_url.
Scope: storage:read
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | File ID (the id from the folder listing, not the attachment_id). |
The download_url is a signed link valid for 10 minutes (expires_in_seconds is 600). Download it with a plain GET, without the Authorization header. Anyone who has the link can download the file until it expires, so don't share or log it; request a new one when you need it.
# 1. Get the file details and a fresh link
curl https://app.tylode.com/api/v1/storage/FILE_ID \
-H "Authorization: Bearer tylode_pat_xxx"
# 2. Download it (use the download_url from step 1)
curl -L -o contract-northwind.pdf "DOWNLOAD_URL"import { writeFile } from "node:fs/promises";
const file = await tylode(`/storage/${FILE_ID}`);
const res = await fetch(file.download_url); // no Authorization header
await writeFile(file.filename, Buffer.from(await res.arrayBuffer()));Response 200
{
"data": {
"id": "5d8f1b4e-7a0c-4c3d-b6f9-2c5a8e1b4d47",
"folder_id": "4c7e0a3d-6f9b-4b2c-a5e8-1b4f7d0a3c36",
"attachment_id": "6e9a2c5f-8b1d-4d4e-a7a0-3d6b9f2c5e58",
"filename": "contract-northwind.pdf",
"mime_type": "application/pdf",
"size_bytes": 248193,
"download_url": "https://…/contract-northwind.pdf?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
"expires_in_seconds": 600
}
}Errors
| Status | When |
|---|---|
404 | The file doesn't exist, isn't yours, is in the trash, or has no stored content. |
Upload a file
POST /api/v1/storage
Uploads a file to My drive, at the top level or in a folder. It works like uploading from the app: the same file types are accepted and it counts toward your plan's storage.
Scope: storage:write
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id | body | string | Yes | The workspace you upload from. My drive is yours, but the file is stored as an attachment of that workspace, so you need to be able to upload files in it. |
filename | body | string | Yes | File name, with its extension. Up to 255 characters; slashes (/ and \) are replaced with _, and control and invisible characters (such as the ones that reverse text direction) are removed. |
content_base64 | body | string | Yes | The file's content, base64-encoded. |
mime_type | body | string | No | File type, such as application/pdf or image/png. It only counts when the extension isn't a known one: for known extensions (.pdf, .png, .docx…), the type comes from the extension. Defaults to application/octet-stream. |
folder_id | body | string | No | My drive folder to save it in, from List a folder. Defaults to the top level. |
Limits:
- Up to 3 MB per file through the API. Upload bigger files from the app.
- The same file types as in the app. HTML pages, SVG, JavaScript and executables can't be uploaded.
- Your plan's storage. If the file doesn't fit, the upload is refused with a message that explains it. See Limits.
curl -X POST https://app.tylode.com/api/v1/storage \
-H "Authorization: Bearer tylode_pat_xxx" \
-H "Content-Type: application/json" \
-d "{
\"workspace_id\": \"WORKSPACE_ID\",
\"filename\": \"invoice-2026-09.pdf\",
\"mime_type\": \"application/pdf\",
\"content_base64\": \"$(base64 < invoice-2026-09.pdf | tr -d '\n')\"
}"import { readFile } from "node:fs/promises";
const content = await readFile("invoice-2026-09.pdf");
const file = await tylode("/storage", {
method: "POST",
body: {
workspace_id: WORKSPACE_ID,
filename: "invoice-2026-09.pdf",
mime_type: "application/pdf",
content_base64: content.toString("base64"),
},
});
console.log(file.id);Response 201
The new file, with the same fields as in List a folder. Use its id to get a download link from Get a file.
{
"data": {
"id": "7f0b3d6a-9c2e-4f5a-b8d1-4e7a0c3f6b69",
"folder_id": "4c7e0a3d-6f9b-4b2c-a5e8-1b4f7d0a3c36",
"attachment_id": "8a1c4e7b-0d3f-4a6c-9b2e-5f8d1a4c7e70",
"position": 0,
"filename": "invoice-2026-09.pdf",
"mime_type": "application/pdf",
"size_bytes": 182344
}
}Errors
| Status | When |
|---|---|
400 | workspace_id, filename or content_base64 is missing, or folder_id isn't a valid ID. |
400 | content_base64 isn't valid base64. |
400 | That file type can't be uploaded. Executables, scripts, HTML and SVG are rejected by their extension, even if you declare another type. |
400 | The upload failed: you can't upload files in that workspace, or the file doesn't fit in your plan's storage. |
404 | The folder doesn't exist, isn't yours, or is in the trash. |
413 | The file is over 3 MB. |
Move a file to the trash
DELETE /api/v1/storage/:id
Moves a file to the My drive trash, the same as deleting it in the app. It can be recovered from there, and while it's in the trash it doesn't appear in List a folder.
Scope: storage:delete
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | File ID (the id from the folder listing, not the attachment_id). |
curl -X DELETE https://app.tylode.com/api/v1/storage/FILE_ID \
-H "Authorization: Bearer tylode_pat_xxx"Response 200
{ "data": { "id": "5d8f1b4e-7a0c-4c3d-b6f9-2c5a8e1b4d47", "papelera": true } }Errors
| Status | When |
|---|---|
400 | The ID isn't valid. |
404 | The file doesn't exist, isn't yours, or is already in the trash. |
What all DELETE endpoints have in common is in Deleting: trash or permanent.
Related
- Whiteboards, Studio and My drive in the app
- Pages API, to open a page you found with search
- Chats, mail and notifications API
- MCP:
search_workspace,list_drive,get_drive_file,upload_drive_fileand the whiteboard and presentation tools use these same endpoints