Developers
Errors and conventions
This page covers the rules every Tylode API endpoint follows: how errors look, what each status code means, how big lists can get and how to handle dates. Knowing them saves you from most surprises when building an integration.
Error format
When something fails, the API returns a non-2xx status code and a JSON body with the message and a fixed code:
{ "error": "Page not found.", "code": "page_not_found" }error is written for people reading logs and can change at any time. It's in the language Accept-Language asks for, out of the 13 app languages (for example Accept-Language: de), in Spanish if you send no header, and in English if you ask for a language we don't have. code doesn't change: if your program needs to tell one error from another, use the status code and code, never the text. Plan limits and other messages from the database have code: "error" and only exist in Spanish.
const res = await fetch(url, { headers });
if (!res.ok) {
const { error } = await res.json().catch(() => ({ error: res.statusText }));
if (res.status === 429) {
// wait for the next minute and retry
}
throw new Error(`Tylode API ${res.status}: ${error}`);
}The OAuth token endpoint is the one exception: it uses the standard OAuth format (error plus error_description). See OAuth 2.0 apps.
Status codes
| Status | Meaning | Typical causes |
|---|---|---|
200 | OK | The request worked. |
201 | Created | A POST created something. The new item is in data. |
400 | Bad request | A required parameter is missing (such as workspace_id, q or title); the body isn't valid JSON; a value has the wrong type; a PATCH has nothing to change; a database row names a column or option that doesn't exist; a date, style or position isn't valid; the file you upload isn't valid base64 or is a type that can't be uploaded; or the write was refused because you can't edit there (a read-only role, a frozen workspace, a plan limit). |
401 | Not authenticated | No Authorization header, or a token that is wrong, revoked or belongs to a deleted OAuth app. |
403 | Forbidden | Plan: the token's owner isn't on Pro or Premium. Scope: an OAuth app or plugin wasn't granted the permission the endpoint needs. Not available to apps: OAuth apps and plugins calling a route that no scope covers. Connect it again: an OAuth app connected before this change (on October 9, 2026) trying to change a card, a whiteboard element or what's inside a slide (code: "reauthorization_required"); the person has to authorize it again. No permission to change it: a PATCH to a database row you can see but not edit, or a DELETE of a board or whiteboard you can see but not move to the trash. |
404 | Not found | The item doesn't exist, or it exists but you can't see it. When deleting, also if it was already in the trash. Unknown paths also return 404, not necessarily with a JSON body. |
413 | Payload too large | The file you upload with POST /storage is over 3 MB. |
429 | Too many requests | More than 60 requests in the current minute. See Rate limits. |
500 | Server error | Tylode couldn't read the data. An ID that isn't a valid UUID can also end here. Check your IDs and retry reads a little later. |
A 401, 403 or 429 means nothing was done, so fixing the cause and repeating the request is always safe.
Things you can't see look empty
The API acts as you and only returns what you can see in the app. For lists, that means things you don't have access to don't produce an error: they're just missing. If you pass the ID of a workspace you're not a member of, GET /pages?workspace_id=… returns { "data": [] } rather than 403 or 404.
If a list comes back empty when you expected results:
- Call
GET /workspacesand check that theworkspace_idis one of yours. - Check that the item is visible to you in the app. Private boards, whiteboards and databases that haven't been shared with you don't appear.
- For OAuth apps, remember that the user who authorized the app is the one whose access counts.
For single items (such as GET /pages/:id), something you can't see returns 404, exactly like something that doesn't exist.
No pagination
List endpoints return everything in one response; there are no cursors or pages. A few endpoints have a fixed size instead:
| Endpoint | Limit |
|---|---|
GET /chats/:id/messages | The latest limit messages, oldest first. limit from 1 to 200, 50 by default. Values out of range are adjusted to the nearest valid one. |
GET /search | Up to limit results. limit from 1 to 50, 20 by default. |
GET /notifications | The 100 most recent. |
Everything else (pages, boards, cards, events, rows, whiteboard items…) comes in full. For big workspaces, ask for less: filter calendar events with from and to, or use search instead of listing everything.
No idempotency keys
The API doesn't support idempotency keys. Sending the same POST twice creates two pages, two cards or two messages.
- Retry
GETrequests freely. - Retry any request that got
401,403or429: nothing was done. - Retry a
DELETEwithout worrying: if it already happened, you get404and nothing else changes. - Don't retry a
POSTblindly after a timeout or a network error. It may have worked. List first (for example, look for a card with that title) and create only if it's missing. - For webhooks, which go the other way, use the delivery ID to spot duplicates. See Webhooks.
Deleting: trash or permanent
DELETE endpoints do the same as deleting in the app:
- Anything with a trash goes to the trash and can be recovered from the app: pages (with their subpages), boards, whiteboards, presentations and My drive files.
- Everything else is deleted for good: cards, events, database rows, slides, and whiteboard and slide elements.
The response tells you which one happened:
{ "data": { "id": "c4f7a0d3-6b9e-4c2f-8a5d-1e4b7c0f3a96", "papelera": false } }id is the ID of what you deleted, and papelera ("trash") is true if it went to the trash or false if it was deleted for good.
- If it doesn't exist, you can't see it, you aren't allowed to delete it, or it was already in the trash, you get
404withcode: "not_found_or_cannot_delete". - If you can see a board or whiteboard but your role doesn't let you move it to the trash, you get
403with the samecode. - OAuth apps need the resource's
:deletescope, which is separate from:write. See Scopes. - Chats, mail, notifications and search have nothing to delete through the API.
Dates and time zones
- Every timestamp the API returns is ISO 8601 in UTC, like
2026-09-23T08:30:00+00:00. Convert to local time in your code. - When you send a date (for example
start_atfor a calendar event, orfromandto), include the offset or aZ:2026-10-01T09:00:00+02:00or2026-10-01T07:00:00Z. A time without an offset is read as UTC. - In query strings, encode the
+of an offset as%2B, or useZto avoid the problem.
CORS: call the API from a server
The API doesn't send CORS headers, so a browser won't let a page on another domain read its responses. That's on purpose: calling the API from the browser would mean putting a token that can do everything in code anyone can read.
- Call the API from your server, a serverless function or a script.
- To show something inside Tylode, build a plugin: it talks to the API through Tylode, without a token.
- For apps used by other people, use OAuth and keep the tokens on your server.
Versioning
All endpoints live under /api/v1. New fields may be added to responses and new endpoints may appear, so write clients that ignore fields they don't recognize and don't depend on the order of keys in a JSON object. Webhook header names are stable.
Related pages
- Authentication: tokens and rate limits.
- Developer platform: conventions and endpoint index.
- Troubleshooting: problems that aren't specific to the API.