Post and read Twist threads, comments and conversations; manage channels, groups, workspace users, reactions and the inbox.
Twist ships in the w6w first-party pack. It declares 111 actions, 3 health checks, and the host runs its code in a sandbox that never sees the credential.
io.w6w.twistTwist is Doist’s async team messaging tool: conversations live in threads inside channels, so a message is a post that can be answered whenever people get to it. This app puts that structure in a workflow.
It covers the working loop of a team that uses Twist: post a thread to a channel, comment on it, close or reopen it, pin or star it, and move it between channels; send direct messages in a conversation; add reactions; and read the inbox, unread threads and search results to decide what needs a reply.
Beyond messaging, the app manages the structure around it — workspaces, channels, groups, and workspace members — plus notification settings, join links, loop-in email addresses and event webhooks, so a workflow can react to Twist events instead of polling for them. Sign in with OAuth for a shared connection, or paste a personal test token for development.
Twist’s file-upload endpoint takes multipart bytes and is not offered, and the user-account endpoints that handle passwords or rotate the API token are deliberately left out, so a workflow can never change or expose the login behind a connection.
Three routes to the same 111 actions. The Workflow tab is generated from Twist'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.
attachment-remove Remove an attachment from a thread, comment or conversation message. Uploading is not offered: it needs multipart file bytes.
conversation-get-or-create Get the conversation with a set of users, creating it if it does not exist.
conversation-mark-read Mark a conversation as read. Give a message id or an object index.
conversation-mark-unread Mark a conversation as unread. Give a message id or an object index.
conversation-unread-list List the user's unread conversations in a workspace.
loop-in-get-or-create Get the email address that posts into a channel, thread or conversation, creating it if needed.
notification-settings-get Get the user's notification settings for a workspace.
notification-settings-update-many Change several notification settings at once.
reaction-get Get the reactions on a thread, comment or conversation message. Twist documents this as a POST.
search Full-text search over threads, conversations and messages in a workspace. Page with the returned `next_cursor_mark`.
search-conversation Search a conversation; returns matching message ids (latest 10,000 at most).
search-thread Search a thread; returns matching comment ids (latest 10,000 at most).
thread-autocomplete Find threads whose title contains some text, across every channel the user can reach.
thread-close Close a thread by posting a comment with `thread_action: close`. Twist has no separate close endpoint.
thread-mark-unread-for-others Mark a thread as unread for everyone else, to draw attention to a change.
thread-reopen Reopen a closed thread by posting a comment with `thread_action: reopen`.
threads-mark-all-read Mark every thread in a workspace or a channel as read. Give one of the two ids.
url-join-get-or-create Get a workspace's URL join link, creating it if needed.
user-get-current Get the user that owns the token making the request. The API token Twist returns on this object is removed.
user-update Update the logged-in user's profile. Password changes and away mode are not exposed.
webhook-subscribe Register a URL that Twist calls whenever the chosen event happens. A 403 means the token's scope does not cover the event.
workspace-user-list List the users of a workspace. Uses /api/v4; the v3 endpoint is deprecated.
workspace-user-local-time-get Get a user's local time. Twist answers a bare date-time string, returned as `result`.
workspace-user-update Change a user's type in a workspace. Twist requires either an email or a user id.
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 Twist's real ids.
{
"manifestVersion": "2",
"name": "twist-example",
"steps": [
{
"id": "channel-create",
"uses": {
"app": "io.w6w.twist",
"action": "channel-create",
"connection": "conn_YOUR_CONNECTION_ID"
},
"with": {
"workspaceId": "<workspaceId>",
"name": "<name>"
}
}
]
}channel-create channel-favorite channel-get channel-list channel-unarchive +106 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 Twist, 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 Twist. 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: "channel-create",
payload: {
workspaceId: "<value>",
name: "<value>",
// tempId: "<value>",
// userIds: "<value>",
// color: "<value>",
// public: "<value>",
// description: "<value>",
// defaultGroups: "<value>",
// defaultRecipients: "<value>",
// isFavorited: "<value>",
// icon: "<value>",
},
});
if (isActionRun(envelope)) console.log(envelope.value); npm install -g @w6w/cli w6w run conn_YOUR_CONNECTION_ID --action channel-create --payload '{"workspaceId":"<value>","name":"<value>"}' Give an AI agent Twist — without giving it Twist'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.twist#channel-create",
"input": {
"workspaceId": "<workspaceId>",
"name": "<name>"
}
}
}
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 Twist 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.
Twist'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. Twist itself is MIT, and the runtime that executes it is source-available (FSL).
Twist declares its own checks, so its health is a property of the app rather than something the host guesses at.
Instatus feed for status.twist.io. Verdict from the `Web application & API` component only; the `Website` component is ignored. Unauthenticated and unsigned.
Unauthenticated GET https://api.twist.com/api/v3/workspaces/get. Twist's JSON auth error (`error_code` + `error_string`) passes — it proves the API is serving. Credential validity is the derived `auth:*` check's job.