Message endpoints
Read a channel's messages page by page, send messages, edit and delete them through the REST API, and understand the message object and pills.
Updated
Paths are relative to https://api.fishcamp.space/api/v1 and all need the X-Workspace-Id header. See the
REST API overview for authentication and errors.
The message object
{
"id": "6c1f0f0e-8a7e-4f4e-9d55-0e6f6f1a2b3c",
"channelId": "0b6f3c55-2a51-4e0a-a3d6-6a3f1f2e9d10",
"kind": "TEXT",
"eventType": null,
"authorType": "MEMBER",
"author": {
"id": "7a1c2e3f-4b5d-4e6f-8a9b-0c1d2e3f4a5b",
"name": "Maya Lopes",
"email": "maya@northstar.studio",
"avatarUrl": null
},
"authorName": null,
"authorAvatarUrl": null,
"content": "@Theo the build for 2.4 is green, can you check the release notes?",
"payload": null,
"embeds": [],
"createdAt": "2026-11-05T14:32:08.000Z",
"editedAt": null,
"deletedAt": null,
"clientId": null
}| Field | Description |
|---|---|
kind | TEXT for messages, EVENT for timeline pills. |
eventType | For pills, what happened (see Pills). null for messages. |
authorType | MEMBER (a person), AGENT (an API key), WEBHOOK (an incoming webhook) or SYSTEM (Fishcamp itself). New values may be added: show unknown ones like SYSTEM. |
author | The person who wrote it, or null when it wasn't a person. |
authorName | The name shown when there's no person, for example the webhook's name. |
content | The text, up to 4,000 characters. Empty for deleted messages. |
payload | Extra data: details of a pill (such as a call's duration) or, for webhook messages, the cards sent (also in embeds). null when there's none. |
embeds | Cards sent by incoming webhooks. |
editedAt, deletedAt | When the message was edited or deleted, or null. |
List messages
GET /channels/:channelId/messages?limit=50&cursor=…| Query | Description |
|---|---|
limit | Messages per page, from 1 to 100. Default: 50. |
cursor | The nextCursor of the previous page, to get older messages. Treat it as an opaque string. |
The first page has the most recent messages. Inside a page, messages go from oldest to newest, ready to display.
{
"items": [{ "id": "…", "kind": "TEXT", "content": "…" }],
"nextCursor": "MjAyNi0xMS0wNVQxNDozMjowOC4wMDBafDZjMWYwZjBl…"
}Keep passing nextCursor as cursor until it comes back null:
curl "https://api.fishcamp.space/api/v1/channels/0b6f3c55-2a51-4e0a-a3d6-6a3f1f2e9d10/messages?limit=100" \
-H "Authorization: Bearer cwk_YOUR_KEY" \
-H "X-Workspace-Id: YOUR_WORKSPACE_ID"Deleted messages stay in the list with deletedAt set and empty content, so a conversation keeps its shape.
Send a message
POST /channels/:channelId/messages| Field | Type | Required | Description |
|---|---|---|---|
content | string | Yes | 1 to 4,000 characters. Trailing spaces and line breaks are trimmed. |
clientId | string | No | Your own ID for the message (8 to 64 letters, digits, _ or -). It comes back in the response, so you can match what you sent. |
curl -X POST "https://api.fishcamp.space/api/v1/channels/0b6f3c55-2a51-4e0a-a3d6-6a3f1f2e9d10/messages" \
-H "Authorization: Bearer cwk_YOUR_KEY" \
-H "X-Workspace-Id: YOUR_WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{"content": "Release 2.5 is out: https://example.com/releases/2.5"}'Returns 201 Created with the message. Everyone in the channel sees it right away. Messages sent with an API key need
the write scope and show the key's name with an AI label.
Edit a message
PATCH /messages/:messageId| Field | Type | Required | Description |
|---|---|---|---|
content | string | Yes | The new text, 1 to 4,000 characters. |
Only the author can edit, and only text messages. Returns the message with editedAt set. Someone else's message gets
403 forbidden.
With an API key (write scope), "the author" means the key itself: a key edits only the messages it posted, and never
the messages its creator wrote as a person.
Delete a message
DELETE /messages/:messageIdThe author can delete their own messages; admins and owners, as people, can delete any message. Returns 204.
Everyone sees "Message deleted" in its place.
An API key deletes only the messages it posted itself, even when an admin created it: anyone else's message gets
403 forbidden.
Pills
Pills are messages with kind: "EVENT". Their eventType says what happened:
eventType | What happened |
|---|---|
channel.created | The channel was created. |
channel.renamed | The channel was renamed (payload.from, payload.to). |
channel.modality_changed | Voice, camera or screen sharing was turned on or off (payload.modality, payload.enabled). |
channel.member_added | Someone was added to the channel, or joined it with an invite link (payload.via is invite_link). |
call.started | The first person joined: the audio room opened. |
call.participant_joined | Someone joined the audio room. For a guest, payload.guest is true and the name ends with "(guest)". |
call.participant_left | Someone left the audio room. |
call.screen_share_started | Someone started sharing their screen. |
call.screen_share_stopped | Someone stopped sharing their screen. |
call.ended | The audio room closed (payload.durationMs, payload.participants). |
webhook.created | An admin created an incoming webhook. |
webhook.revoked | An admin revoked an incoming webhook. |
transcript.started | Transcription started in the audio room. New in November 2026 |
transcript.routed | The transcript was saved, sent to agents or discarded, failed, or is waiting for a person. New in November 2026 |
Pills can't be edited. Treat unknown eventType values as plain information: new ones may be added.
Channel endpoints
List, create, update and archive channels, switch voice, camera, screen and transcription, open direct messages and add people through the REST API.
Audio room endpoints
See who is in a channel's audio room, list the rooms that are open, and get a token to join one with a WebRTC client.