Manage a Heartbeat community — members, groups, channels, threads, events, courses and documents.
Heartbeat ships in the w6w first-party pack. It declares 54 actions, 2 health checks, and the host runs its code in a sandbox that never sees the credential.
io.w6w.heartbeatHeartbeat (heartbeat.chat) is a community platform: members, groups, discussion channels, live/voice channels, events, direct messages, courses and a document wiki, all inside one paid community. This app covers the full surface of Heartbeat’s own public REST API, verified directly against the vendor’s OpenAPI 3.0 document embedded in its ReadMe-hosted reference (fetched 2026-09-05, info.version 1.0.0) plus live probes against api.heartbeat.chat and status.heartbeat.chat.
Membership: list, get, create, update, delete and reactivate users; look a user up by email; invitations and pending-user seats; roles; groups and group membership. Structure: channel categories, post/chat/voice channels. Content: threads, comments, direct chats and direct messages, chat-channel messages, events (including recurring instances and attendance), webhooks for reacting to community activity, courses and lessons, native videos, and the document wiki.
Every text field Heartbeat accepts (thread/comment/message bodies) is a restricted HTML subset — , , , -, , , — not Markdown; anything else is silently stripped, and this app’s params say so rather than let that surprise a workflow author.
Three routes to the same 54 actions. The Workflow tab is generated from Heartbeat's own manifest and carries its real ids, so it is copy-pasteable; the Code and CLI examples are the same call for any action on any app, so every app-specific value in them is a blank you fill in.
create-channel Create a new POSTS (thread) or CHAT channel. Thread channels are created by default.
create-direct-chat Create a direct chat between two users. If one already exists, its id & URL are returned instead of creating a duplicate.
create-event Create a new event. If both Invited users and Invited groups are empty, the event is open to the whole community.
create-invitation Create a new invitation link. Members joining via it get this role and these groups.
create-lesson Create a lesson (text only — no video hero or embed cards; see this action's own docs for why those are left out).
create-pending-user Seat a member before they ever log in. On their first login they become a real member with these attributes. Calling this again for the same still-pending email updates it instead of erroring; calling it for an email that is already a real member returns an error.
create-user Create a new member in the community directly (no invitation email is required).
delete-user Remove a member from the community. Their threads, comments and messages are kept — only access is revoked. Reversible with Reactivate User.
get-event-attendance Return attendance for the last 10 instances of an event. A non-recurring event returns one entry.
get-notifications Get the number of pending notifications a specific member has.
list-channels Return every channel in the community — POSTS (thread), CHAT and VOICE channels.
list-chat-channel-messages Page through a CHAT channel's message history, oldest-cursor forward.
list-courses Return every course in the community, including its cohort/module/lesson tree.
list-direct-messages Return the 100 most recent messages from a direct chat. Only reachable when the chat includes at least one admin user.
list-documents Page through the document wiki index. Heartbeat gives no `hasMore` signal here — a page shorter than the requested Limit is the only reliable end-of-list indicator.
list-event-instances Return every occurrence of a recurring event, past and future.
list-events Return every event in the community, optionally filtered to one invited group.
list-threads Return the 20 most recent threads in a channel. Not paginated beyond that.
list-users Return every member of the Heartbeat community. Heartbeat paginates nothing here — this is the full member list in one response.
update-lesson Edit a lesson's title, content or publish status. Only provided fields change; an existing video hero or embed cards are left untouched.
update-user Update a member's profile fields, addressed by email. Only provided fields change.
update-voice-channel Edit a voice channel. All fields are optional; only provided fields change.
A workflow step names the app and the action, and the editor fills in the
connection when you pick one. This is the Step shape from the
workflow spec, carrying Heartbeat's real ids.
{
"manifestVersion": "2",
"name": "heartbeat-example",
"steps": [
{
"id": "add-to-group",
"uses": {
"app": "io.w6w.heartbeat",
"action": "add-to-group",
"connection": "conn_YOUR_CONNECTION_ID"
},
"with": {
"groupID": "<groupID>",
"emails": "<emails>"
}
}
]
}add-to-group create-channel create-channel-category create-chat-message create-comment +49 more actions available
Every app-specific value here is a blank you have to fill in. An
app action is reached through the connection that authenticates it, so the
address is a connection id, not the app id — and connections belong to your account,
so a public page cannot know yours. Create one for Heartbeat, then fill in
the three blanks: conn_YOUR_CONNECTION_ID, the action key, and the
parameters that action declares. The call itself is real — the shape is transcribed
from the studio's own snippet builder, which prints the same kind of blanks — but
nothing in it is specific to Heartbeat. The Workflow tab is where this app's
real ids are.
npm install @w6w/sdkyarn add @w6w/sdkpnpm add @w6w/sdkdeno add npm:@w6w/sdkimport { W6wClient, isActionRun } from "@w6w/sdk";
// Reads W6W_BASE_URL and W6W_TOKEN from the environment when omitted.
const client = new W6wClient();
const envelope = await client.run({
urn: "conn_YOUR_CONNECTION_ID",
action: "add-to-group",
payload: {
groupID: "<value>",
emails: "<value>",
// shouldRemoveFromSiblingGroups: "<value>",
},
});
if (isActionRun(envelope)) console.log(envelope.value); npm install -g @w6w/cli w6w run conn_YOUR_CONNECTION_ID --action add-to-group --payload '{"groupID":"<value>","emails":"<value>"}' Give an AI agent Heartbeat — without giving it Heartbeat's credentials. One MCP endpoint exposes every app, function and workflow the caller is entitled to, as tools it can discover and run. Access is granted per team while we onboard.
One tool call{
"name": "w6w_invoke",
"arguments": {
"ref": "app:io.w6w.heartbeat#add-to-group",
"input": {
"groupID": "<groupID>",
"emails": "<emails>"
}
}
}
Every tool names its target with a single ref. The
app: form above doesn't name a connection at all — the
host resolves which of the caller's Heartbeat connections to sign
with, and refuses rather than guesses when the answer is ambiguous.
The token is attached host-side, at the moment of the call. It is never a tool argument, never in the model's context, and never in a transcript — so a prompt injection has nothing to exfiltrate.
Tools are derived per end user from what that person has actually connected and is entitled to — not one shared bot identity carrying the union of everyone's access.
Multi-step work runs on the workflow engine and returns a run handle the agent can poll — retries, branching and state survive the conversation that started them.
Heartbeat's declared health checks are on the surface too, so an agent can tell "the vendor is down" from "your credential expired" before it burns a retry on either.
The MCP surface is part of the hosted platform. Heartbeat itself is MIT, and the runtime that executes it is source-available (FSL).
Heartbeat declares its own checks, so its health is a property of the app rather than something the host guesses at.
Reads the "Heartbeat Communities" component from status.heartbeat.chat. Heartbeat has no separately-tracked API component — this backend serves both the web community product and this REST API — so that component is the signal for both. The page's other component, Mobile Apps, is dropped: a native-app outage says nothing about REST API availability.
Reads x-ratelimit-limit/remaining/reset off GET /v0/roles. Heartbeat documents only "10 requests/second"; the headers reveal the real window is a rolling ~2 seconds (limit 20 over 2s = 10/s average).