Slack

Lightweight, dependency-free, in-memory fake of the Slack Web API for testing code that uses the real @slack/web-api WebClient (and the language-agnostic Slack Web API).

Default port: 4654

Quick start

Start the server:

import { SlackServer } from "./services/slack/src/server.js";

const server = new SlackServer(4654);
await server.start();
// ... run your app/tests ...
await server.stop();

Point the real @slack/web-api client at it. The WebClient reads its base URL from the slackApiUrl option, so override it to the parlel fake:

import { WebClient } from "@slack/web-api";

const web = new WebClient("xoxb-parlel-test-token", {
  slackApiUrl: "http://127.0.0.1:4654/api/", // point at the parlel fake
});

const res = await web.chat.postMessage({
  channel: "C_GENERAL1",
  text: "hello from parlel",
});
// res.ok === true, res.ts === "<seconds>.<micros>", res.channel === "C_GENERAL1"

const history = await web.conversations.history({ channel: "C_GENERAL1" });
// history.messages[0].text === "hello from parlel"

Every posted message, channel, file, reaction, etc. is captured in memory and can be inspected via the /__parlel/* endpoints (see below). The whole world is resettable.

Wire protocol

The fake speaks the exact Slack Web API wire protocol that @slack/web-api uses:

Implemented operations

Auth & misc

chat

conversations

users

reactions

pins

bookmarks

files

views (Block Kit surfaces)

usergroups

parlel control / inspection endpoints

These are not part of Slack — they are parlel extensions for test setup and assertions.

Method & pathPurpose
POST /__parlel/resetWipe all state and re-seed defaults (workspace, bot, #general, #random, Alice)
GET /__parlel/messagesEvery stored message across all channels ({ messages, count })
GET /__parlel/channelsEvery channel including private members ({ channels, count })
GET /__parlel/filesEvery stored file ({ files, count })
POST /__parlel/usersAdd a user fixture ({ id?, name, email?, is_bot?, is_admin? })
POST /__parlel/upload/:idSimulated external-upload PUT target used by files.uploadV2
GET /healthHealth check ({ "status": "ok" })
GET /Service metadata

Default seeded state

After start() (or reset) the world contains:

Surface coverage

This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.

Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.

FeatureSupportedNotes
chat.* messagingpost / update / delete / ephemeral / schedule / permalink / meMessage
Bot message shape (subtype, username)bot-token posts return subtype: "bot_message", username, bot_id
Threads & repliesthread_ts, reply counts, conversations.replies
conversations.* channel lifecyclecreate / join / leave / invite / kick / archive / rename / topic / purpose
Channel object fidelityis_member, is_shared, is_ext_shared, is_org_shared, unlinked, pending_shared, previous_names
Granular channel-name validationinvalid_name_required / invalid_name_maxlength / invalid_name_specials / invalid_name_punctuation
DMs & multi-party IMsvia conversations.open
users.* directory & presencelist / info / lookupByEmail / presence / profile
Reactions, pins, bookmarksfull CRUD
Files (legacy upload + uploadV2)content captured in-memory; binary bytes simulated
Block Kit views (modals + App Home)open / push / publish / update
Usergroups (subteams)create / list / update / membership
Form-encoded and JSON bodiesmatches the SDK default + JSON callers
Token / auth checkingnot_authed, invalid_auth, token_revoked
Pagination cursorsresponse_metadata.next_cursor returned as "" (single page); limit/has_more honored on conversations.history
Rate limiting (HTTP 429 / Retry-After)By design — never throttles, so local tests run at full speed, zero cost
Request signing / x-slack-signature verificationRoadmap
Socket Mode / RTM / Events API / webhooksRoadmap
admin.* (Enterprise Grid)Roadmap
oauth.*, apps.*, dnd.*, stars.*, search.*Roadmap

Error codes / shapes

All errors are returned as HTTP 200 with { "ok": false, "error": "<code>" }. Common codes:

CodeWhen
not_authedNo token supplied
invalid_authUnknown token
token_revokedToken was revoked via auth.revoke
unknown_methodMethod not implemented
channel_not_foundChannel id/name does not resolve
no_textchat.postMessage with no text/blocks/attachments
message_not_foundts does not match a stored message
thread_not_foundthread_ts parent missing
user_not_found / users_not_foundUnknown user / email lookup miss
name_takenconversations.create/rename with a name already in use
invalid_name_requiredconversations.create/rename with an empty name
invalid_name_maxlengthChannel name longer than 80 characters
invalid_name_specialsChannel name with uppercase or otherwise disallowed characters
invalid_name_punctuationChannel name made only of punctuation (no alphanumerics)
already_archived / not_archived / cant_archive_generalArchive lifecycle
already_reacted / no_reactionreactions.add/remove
already_pinned / no_pinpins.add/remove
bookmark_not_foundbookmarks.edit/remove
file_not_found / no_file_dataFiles API
invalid_presenceusers.setPresence with a value other than auto/away
time_in_past / invalid_scheduled_message_idScheduled messages
no_such_subteamUsergroups
invalid_argumentsMissing required arguments (e.g. views.open without trigger_id)

The image target stays tiny (<1MB): a single pure-Node.js file, no external npm dependencies.

<!-- parlel:testenv:start -->

Configuration — test.env

Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.

SLACK_BOT_TOKEN=xoxb-parlel-test-token
SLACK_APP_TOKEN=xapp-parlel-test-token
SLACK_SIGNING_SECRET=parlel_test_signing_secret
SLACK_BASE_URL=http://parlel-bridge:4654/api/
<!-- parlel:testenv:end -->