Developers
Chats, mail and notifications API
These endpoints cover Tylode's communication tools: chat conversations, internal mail (Mail) and your notifications. Everything you send through them is sent as you, exactly as if you'd typed it 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/chats | List your conversations | chats:read |
| GET | /api/v1/chats/:id/messages | Read the latest messages of a conversation | chats:read |
| POST | /api/v1/chats/:id/messages | Send a message | chats:write |
| GET | /api/v1/mail | List your mail threads | mail:read |
| POST | /api/v1/mail | Start a mail thread | mail:write |
| GET | /api/v1/mail/:id | Get a thread with its messages | mail:read |
| POST | /api/v1/mail/:id | Reply to a thread | mail:write |
| PATCH | /api/v1/mail/:id | Mark a thread read/unread or archive it | mail:write |
| GET | /api/v1/notifications | List your notifications | notifications:read |
| POST | /api/v1/notifications | Mark all your notifications as read | notifications:write |
Scopes only matter for OAuth apps.
Where to get user IDs
Several fields take or return user IDs: mentions in chat messages and recipient_ids in mail, for example. There's no endpoint that lists workspace members or turns a user ID into a name or an email. You collect user IDs from other responses:
| Where | Field |
|---|---|
| Chat messages | sender_id, mentions |
| Conversations | created_by |
| Mail threads and mail messages | created_by, sender_id |
| Notifications | actor_id (who caused it), recipient_id (you) |
| Whiteboards, presentations | created_by, owner_id |
| Database rows | values of person columns |
| Webhook payloads | sender_id (message.created), user_id (member.joined), created_by (page.created, comment.created) |
A practical way to build a directory: read the recent messages of a group chat everyone is in, and map each sender_id to the person you know wrote it. Your own ID is the recipient_id of any of your notifications, or the sender_id of a message you sent.
Chats
Chats are the conversations under Chats in the app: direct messages and groups. You can only read and write in conversations you're part of. See Chats.
List conversations
GET /api/v1/chats
Returns the conversations you're in within a workspace, most recently created first.
Scope: chats:read
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id | query | string | Yes | The workspace. |
Each conversation has id, workspace_id, is_group (false for a direct message), name (null for direct messages), created_by, created_at and last_read_at (when you last read it).
curl "https://app.tylode.com/api/v1/chats?workspace_id=WORKSPACE_ID" \
-H "Authorization: Bearer tylode_pat_xxx"Response 200
{
"data": [
{
"id": "3c8a1e6f-9d2b-4f5a-a7e4-1b6c9d3f0e25",
"workspace_id": "7c1e2a4b-3f5d-4e8a-9b21-6d0f5c8e1a37",
"is_group": true,
"name": "Marketing",
"created_by": "6a1d8f3c-4e9b-4b2a-8c7f-5d0e2a9b1c63",
"created_at": "2026-09-02T09:12:44.107512+00:00",
"last_read_at": "2026-09-23T07:58:10.422091+00:00"
},
{
"id": "8b3e6a1d-4c7f-4e2b-9d5a-0f3c6e9b2a74",
"workspace_id": "7c1e2a4b-3f5d-4e8a-9b21-6d0f5c8e1a37",
"is_group": false,
"name": null,
"created_by": "2f7c9e41-8b3a-4d6e-a1f5-0c8e3b7d9a52",
"created_at": "2026-08-14T16:40:03.918377+00:00",
"last_read_at": "2026-09-22T18:21:37.004218+00:00"
}
]
}Errors
| Status | When |
|---|---|
400 | workspace_id is missing. |
There's no endpoint to create conversations or add people to them; do that in the app.
Read messages
GET /api/v1/chats/:id/messages
Returns the latest messages of a conversation, in chronological order (oldest of the batch first, newest last).
Scope: chats:read
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Conversation ID. |
limit | query | number | No | How many of the latest messages to return, from 1 to 200. Defaults to 50. Values outside the range are clamped. |
Each message has id, conversation_id, sender_id, body, attachment_id (null if there's no file), reply_to_message_id (the message it answers, or null), mentions (user IDs) and created_at. There's no pagination: you can read at most the last 200 messages. Attached files can't be downloaded through the API.
curl "https://app.tylode.com/api/v1/chats/CONVERSATION_ID/messages?limit=20" \
-H "Authorization: Bearer tylode_pat_xxx"const messages = await tylode(`/chats/${CONVERSATION_ID}/messages?limit=20`);
const last = messages.at(-1); // newestResponse 200
{
"data": [
{
"id": "d1a4c7e0-3f6b-4a9d-8c2e-5b8f1a4d7c09",
"conversation_id": "3c8a1e6f-9d2b-4f5a-a7e4-1b6c9d3f0e25",
"sender_id": "6a1d8f3c-4e9b-4b2a-8c7f-5d0e2a9b1c63",
"body": "Who's reviewing the newsletter?",
"attachment_id": null,
"reply_to_message_id": null,
"mentions": [],
"created_at": "2026-09-23T08:02:15.771093+00:00"
},
{
"id": "e2b5d8f1-4a7c-4b0e-9d3f-6c9a2b5e8d10",
"conversation_id": "3c8a1e6f-9d2b-4f5a-a7e4-1b6c9d3f0e25",
"sender_id": "2f7c9e41-8b3a-4d6e-a1f5-0c8e3b7d9a52",
"body": "I'll do it. @Laura can you send me the final images?",
"attachment_id": null,
"reply_to_message_id": "d1a4c7e0-3f6b-4a9d-8c2e-5b8f1a4d7c09",
"mentions": ["9e4b2c7a-1f6d-4a8e-b3c9-7a5f0d2e8b14"],
"created_at": "2026-09-23T08:05:47.130256+00:00"
}
]
}A conversation you're not in returns an empty list, not an error.
Send a message
POST /api/v1/chats/:id/messages
Posts a message in a conversation you're part of, as you.
Scope: chats:write
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Conversation ID. |
body | body | string | Yes | Message text. Can't be empty or only spaces. |
reply_to_message_id | body | string | No | ID of a message in the same conversation to reply to. |
mentions | body | array of strings | No | User IDs to mention. |
How mentions and replies notify people:
- Each user in
mentionsgets amentionnotification, if they're in the conversation and aren't you. Write the name inbodyas you want it to read (for example@Laura); thementionsarray is what triggers the notification. - Replying to someone else's message sends them a
replynotification, unless you already mentioned them in the same message.
curl -X POST https://app.tylode.com/api/v1/chats/CONVERSATION_ID/messages \
-H "Authorization: Bearer tylode_pat_xxx" \
-H "Content-Type: application/json" \
-d '{
"body": "@Laura the build is green, ready to publish.",
"mentions": ["9e4b2c7a-1f6d-4a8e-b3c9-7a5f0d2e8b14"]
}'const message = await tylode(`/chats/${CONVERSATION_ID}/messages`, {
method: "POST",
body: {
body: "@Laura the build is green, ready to publish.",
mentions: ["9e4b2c7a-1f6d-4a8e-b3c9-7a5f0d2e8b14"],
},
});Response 201
{
"data": {
"id": "f3c6e9a2-5b8d-4c1f-a0e4-7d0b3c6f9e21",
"conversation_id": "3c8a1e6f-9d2b-4f5a-a7e4-1b6c9d3f0e25",
"sender_id": "2f7c9e41-8b3a-4d6e-a1f5-0c8e3b7d9a52",
"body": "@Laura the build is green, ready to publish.",
"attachment_id": null,
"reply_to_message_id": null,
"mentions": ["9e4b2c7a-1f6d-4a8e-b3c9-7a5f0d2e8b14"],
"created_at": "2026-09-23T10:14:02.583917+00:00"
}
}Errors
| Status | When |
|---|---|
400 | body is missing or blank. |
400 | The message couldn't be sent (for example, you're not in that conversation). |
Messages in group chats fire the message.created webhook event; direct messages never do.
This is Tylode's internal mail between people, the one in Mail; it isn't email. Each person has their own read and archived state for every thread. See Mail.
List mail threads
GET /api/v1/mail
Returns the threads you take part in that were started in a workspace, most recently updated first.
Scope: mail:read
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id | query | string | Yes | Workspace the threads were started in. |
archived | query | string | No | Leave it out for threads that aren't archived. true for archived threads only; all for both. |
Each thread has id, workspace_id, subject, created_by, created_at, updated_at, and your own is_read and archived.
curl "https://app.tylode.com/api/v1/mail?workspace_id=WORKSPACE_ID" \
-H "Authorization: Bearer tylode_pat_xxx"const unread = (await tylode(`/mail?workspace_id=${WORKSPACE_ID}`)).filter((t) => !t.is_read);Response 200
{
"data": [
{
"id": "4d9b2e7a-0c5f-4a3d-b8e1-6f2a9c4e7b35",
"workspace_id": "7c1e2a4b-3f5d-4e8a-9b21-6d0f5c8e1a37",
"subject": "Holiday calendar 2027",
"created_by": "6a1d8f3c-4e9b-4b2a-8c7f-5d0e2a9b1c63",
"created_at": "2026-09-22T11:30:18.264850+00:00",
"updated_at": "2026-09-23T09:02:51.019734+00:00",
"is_read": false,
"archived": false
}
]
}Errors
| Status | When |
|---|---|
400 | workspace_id is missing. |
Start a mail thread
POST /api/v1/mail
Creates a thread with its first message and sends it to the recipients.
Scope: mail:write
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id | body | string | Yes | Workspace the thread is started from. |
subject | body | string | Yes | Subject. Can't be blank. |
body | body | string | Yes | First message. Can't be blank. |
recipient_ids | body | array of strings | Yes | User IDs of the recipients, at least one. See Where to get user IDs. |
You're added to the thread automatically, marked as read; each recipient gets it unread.
curl -X POST https://app.tylode.com/api/v1/mail \
-H "Authorization: Bearer tylode_pat_xxx" \
-H "Content-Type: application/json" \
-d '{
"workspace_id": "WORKSPACE_ID",
"subject": "Weekly report",
"body": "Hi both, this week we closed 14 tickets and shipped the new onboarding.",
"recipient_ids": ["6a1d8f3c-4e9b-4b2a-8c7f-5d0e2a9b1c63", "9e4b2c7a-1f6d-4a8e-b3c9-7a5f0d2e8b14"]
}'const thread = await tylode("/mail", {
method: "POST",
body: {
workspace_id: WORKSPACE_ID,
subject: "Weekly report",
body: "Hi both, this week we closed 14 tickets and shipped the new onboarding.",
recipient_ids: [MARC_ID, LAURA_ID],
},
});Response 201
{
"data": {
"id": "5e0c3f8b-1d6a-4b4e-a9f2-7a3b0d5f8c46",
"workspace_id": "7c1e2a4b-3f5d-4e8a-9b21-6d0f5c8e1a37",
"subject": "Weekly report",
"created_by": "2f7c9e41-8b3a-4d6e-a1f5-0c8e3b7d9a52",
"created_at": "2026-09-23T10:20:07.441285+00:00",
"updated_at": "2026-09-23T10:20:07.441285+00:00",
"messages": [
{
"id": "6f1d4a9c-2e7b-4c5f-b0a3-8b4c1e6a9d57",
"thread_id": "5e0c3f8b-1d6a-4b4e-a9f2-7a3b0d5f8c46",
"sender_id": "2f7c9e41-8b3a-4d6e-a1f5-0c8e3b7d9a52",
"body": "Hi both, this week we closed 14 tickets and shipped the new onboarding.",
"attachment_id": null,
"created_at": "2026-09-23T10:20:07.583102+00:00"
}
]
}
}Errors
| Status | When |
|---|---|
400 | workspace_id, subject or body is missing or blank. |
400 | recipient_ids is missing or empty. |
400 | The thread couldn't be created. Nothing is left half-sent. |
Get a mail thread
GET /api/v1/mail/:id
Returns a thread with all its messages, oldest first.
Scope: mail:read
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Thread ID. |
The thread has id, workspace_id, subject, created_by, created_at, updated_at and messages. Each message has id, thread_id, sender_id, body, attachment_id and created_at. Reading a thread doesn't mark it as read; use PATCH for that.
curl https://app.tylode.com/api/v1/mail/THREAD_ID \
-H "Authorization: Bearer tylode_pat_xxx"Response 200
{
"data": {
"id": "4d9b2e7a-0c5f-4a3d-b8e1-6f2a9c4e7b35",
"workspace_id": "7c1e2a4b-3f5d-4e8a-9b21-6d0f5c8e1a37",
"subject": "Holiday calendar 2027",
"created_by": "6a1d8f3c-4e9b-4b2a-8c7f-5d0e2a9b1c63",
"created_at": "2026-09-22T11:30:18.264850+00:00",
"updated_at": "2026-09-23T09:02:51.019734+00:00",
"messages": [
{
"id": "7a2e5b0d-3f8c-4d6a-a1b4-9c5d2f7b0e68",
"thread_id": "4d9b2e7a-0c5f-4a3d-b8e1-6f2a9c4e7b35",
"sender_id": "6a1d8f3c-4e9b-4b2a-8c7f-5d0e2a9b1c63",
"body": "Please add your days off before Friday.",
"attachment_id": null,
"created_at": "2026-09-22T11:30:18.402117+00:00"
}
]
}
}Errors
| Status | When |
|---|---|
404 | The thread doesn't exist, or you're not part of it. |
Reply to a thread
POST /api/v1/mail/:id
Adds your reply to a thread you're part of.
Scope: mail:write
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Thread ID. |
body | body | string | Yes | Reply text. Can't be blank. |
curl -X POST https://app.tylode.com/api/v1/mail/THREAD_ID \
-H "Authorization: Bearer tylode_pat_xxx" \
-H "Content-Type: application/json" \
-d '{ "body": "Done, I have added mine." }'Response 201
{
"data": {
"id": "8b3f6c1e-4a9d-4e7b-b2c5-0d6e3a8c1f79",
"thread_id": "4d9b2e7a-0c5f-4a3d-b8e1-6f2a9c4e7b35",
"sender_id": "2f7c9e41-8b3a-4d6e-a1f5-0c8e3b7d9a52",
"body": "Done, I have added mine.",
"attachment_id": null,
"created_at": "2026-09-23T10:31:44.896520+00:00"
}
}Errors
| Status | When |
|---|---|
400 | body is missing or blank. |
400 | The reply couldn't be saved (for example, you're not part of that thread). |
Mark a thread read or archived
PATCH /api/v1/mail/:id
Changes your own state for a thread: read or unread, archived or not. Other participants aren't affected. On the MCP server, it's the update_mail_thread tool.
Scope: mail:write
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | Thread ID. |
is_read | body | boolean | No* | true to mark as read, false for unread. |
archived | body | boolean | No* | true to archive, false to unarchive it. |
* Send at least one of them, as a real boolean (true, not "true").
curl -X PATCH https://app.tylode.com/api/v1/mail/THREAD_ID \
-H "Authorization: Bearer tylode_pat_xxx" \
-H "Content-Type: application/json" \
-d '{ "is_read": true, "archived": true }'Response 200
The response echoes what you changed:
{
"data": {
"thread_id": "4d9b2e7a-0c5f-4a3d-b8e1-6f2a9c4e7b35",
"is_read": true,
"archived": true
}
}Errors
| Status | When |
|---|---|
400 | Neither is_read nor archived was sent as a boolean. |
Notifications
Notifications are the ones in Activity: mentions, replies, cards assigned to you and pages shared with you. See Notifications.
List notifications
GET /api/v1/notifications
Returns your notifications in a workspace, newest first. By default, only unread ones.
Scope: notifications:read
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id | query | string | Yes | The workspace. |
include_read | query | string | No | true to include notifications you've already read. |
You get at most the 100 most recent notifications. Each one has:
| Field | Description |
|---|---|
id | Notification ID. |
recipient_id | You. |
workspace_id | The workspace. |
actor_id | Who caused it, or null if that account no longer exists. |
kind | mention, reply, card_assigned or page_shared. |
target_type | What it points to: message, voice_message, card or page. |
target_id | ID of that message, voice channel message, card or page. |
context_id | Where the target lives: the conversation for message, the voice channel for voice_message, null otherwise. |
preview | A short excerpt (up to 140 characters): the message text, the card title or the page title. |
read_at | When you read it, or null. |
created_at | When it was created. |
curl "https://app.tylode.com/api/v1/notifications?workspace_id=WORKSPACE_ID" \
-H "Authorization: Bearer tylode_pat_xxx"const pending = await tylode(`/notifications?workspace_id=${WORKSPACE_ID}`);
const mentions = pending.filter((n) => n.kind === "mention");Response 200
{
"data": [
{
"id": "9c4a7d2f-5b0e-4f8c-a3d6-1e7b4c9f2a80",
"recipient_id": "2f7c9e41-8b3a-4d6e-a1f5-0c8e3b7d9a52",
"workspace_id": "7c1e2a4b-3f5d-4e8a-9b21-6d0f5c8e1a37",
"actor_id": "9e4b2c7a-1f6d-4a8e-b3c9-7a5f0d2e8b14",
"kind": "mention",
"target_type": "message",
"target_id": "0d5b8e3a-6c1f-4a9d-b4e7-2f8c5d0a3b91",
"context_id": "3c8a1e6f-9d2b-4f5a-a7e4-1b6c9d3f0e25",
"preview": "@Ana the final images are in the shared folder",
"read_at": null,
"created_at": "2026-09-23T10:40:12.338461+00:00"
},
{
"id": "a0d5b8e3-6c1f-4a9d-b4e7-2f8c5d0a3b92",
"recipient_id": "2f7c9e41-8b3a-4d6e-a1f5-0c8e3b7d9a52",
"workspace_id": "7c1e2a4b-3f5d-4e8a-9b21-6d0f5c8e1a37",
"actor_id": "6a1d8f3c-4e9b-4b2a-8c7f-5d0e2a9b1c63",
"kind": "card_assigned",
"target_type": "card",
"target_id": "c4f7a0d3-6b9e-4c2f-8a5d-1e4b7c0f3a96",
"context_id": null,
"preview": "Fix the signup email",
"read_at": null,
"created_at": "2026-09-23T09:15:40.702583+00:00"
}
]
}Errors
| Status | When |
|---|---|
400 | workspace_id is missing. |
Mark all notifications as read
POST /api/v1/notifications
Marks all your unread notifications in a workspace as read and tells you how many there were. There's no endpoint to mark a single notification.
Scope: notifications:write
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id | body | string | Yes | The workspace. |
curl -X POST https://app.tylode.com/api/v1/notifications \
-H "Authorization: Bearer tylode_pat_xxx" \
-H "Content-Type: application/json" \
-d '{ "workspace_id": "WORKSPACE_ID" }'Response 200
{
"data": { "marked": 2 }
}Errors
| Status | When |
|---|---|
400 | workspace_id is missing, or the notifications couldn't be updated. |
Related
- Chats, Mail and Notifications in the app
- Webhooks, to react to new group chat messages as they happen
- Search API, which also finds mail threads and chat messages
- MCP, to let an assistant read and answer your messages