tneenwh
v0.1.7
Published
Official-style client for the TNEENWH WhatsApp HTTP API — auth, sessions, messaging, webhooks, and React helpers.
Downloads
42
Maintainers
Readme
tneenwh (npm)
TypeScript / JavaScript client for the TNEENWH WhatsApp HTTP API — authentication, OTP signup, sessions, outbound messaging, webhooks, typing indicators, groups (when the server exposes them), and optional React helpers.
- npm:
npm install tneenwh— usesfetch(Node 18+, browsers, Deno, Bun). - HTTP contract:
openapi.jsonon your API host (e.g.GET https://api.tneenwh.com/openapi.json). Swagger UI is often at/api-docs/.
This README mirrors the structure of the Python package README (pip install tneenwh) so you can find the same concepts in both libraries.
Table of contents
- Install
- Imports
- API base URL
- Setup every script needs
TneenwhClient— configuration- Health & authentication
- Account & credentials
- OpenAPI & docs index
- Sessions (panel JWT)
- Messaging —
SessionApi(client.session) - Module-style API (
tneenwh) - Sub-API —
V1Channel - Groups
- OTP helper (no HTTP)
advanced— not on stock REST mapping- Errors
- React / Next.js
- Utilities
- More documentation
Install
npm install tneenwhFrom this monorepo:
npm install ./packages/tneenwh-sdkImports
// Class API + types + errors
import {
TneenwhClient,
SessionApi,
V1Channel,
TneenwhApiError,
FeatureNotSupportedError,
isTneenwhApiError,
isFeatureNotSupportedError,
configure,
tneenwh,
formatOtpNotificationMessage,
phoneToWhatsAppJid,
CHAT_JID_RE,
requestSignupOtp
} from 'tneenwh';
import type {
SendMessagePayload,
SendMessageResponse,
ChatStatePayload,
WebhookEventType,
LoginBody
} from 'tneenwh';
// Optional React provider / hook
import { TneenwhProvider, useTneenwh } from 'tneenwh/react';API base URL
Use your deployment origin as baseUrl (no trailing slash):
https://api.tneenwh.com
Same host as the dashboard. Paths like /me/... are appended by the client.
MCP (Model Context Protocol)
TNEENWH exposes MCP (Streamable HTTP) at POST /me/mcp so Cursor / Claude Desktop (and other MCP hosts) can call tools against your deployment.
This SDK ships small helpers for building the MCP URL and optional focus header:
import { panelMcpUrl, mcpFocusSessionsHeader } from 'tneenwh';
const url = panelMcpUrl('https://api.tneenwh.com'); // https://api.tneenwh.com/me/mcp
const headers = {
Authorization: 'Bearer <panel JWT>',
...mcpFocusSessionsHeader('session-uuid-1, session-uuid-2')
};The dashboard MCP page can also generate a production-ready mcp.json snippet with default session headers.
Setup every script needs
With TneenwhClient: set baseUrl, call auth.login, then create session(sessionId, channelSecret) for sends.
With the tneenwh module: configure, auth.login, setSession(sessionId, channelSecret), then sendText / sendMessage, etc.
Session UUID and channel secret come from the dashboard (GET /me/sessions or create flow).
TneenwhClient — configuration
| Member | Purpose |
|--------|---------|
| new TneenwhClient({ baseUrl, bearerToken?, apiKey?, credentials?, fetchImpl? }) | API origin; optional JWT and Sub-API key |
| setBearerToken(token) | Panel JWT (Authorization: Bearer) |
| setApiKey(key) | Hex API key for /v1/... |
| getBearerToken() / getApiKey() | Current tokens |
| httpRequest(method, path, init?) | Low-level JSON |
| httpRequestBinary(method, path, init?) | Binary (e.g. inbound media) |
Health & authentication
const client = new TneenwhClient({ baseUrl: 'https://api.tneenwh.com' });
await client.health(); // GET /health — often unauthenticated
await client.auth.signupStart({
name: 'Ada',
phone: '+10000000000',
email: '[email protected]',
password: 'SecurePass123'
});
await client.auth.signupVerify({ email: '[email protected]', code: '123456' });
await client.auth.login({ email: '[email protected]', password: 'SecurePass123' }); // stores JWT on client
await client.auth.logout();Module aliases on tneenwh: signupSendOtp, generateOtp → auth.signupStart; verifyOtp → auth.signupVerify.
Account & credentials
Requires login first (Bearer).
await client.me();
await client.profile();
await client.channelSecrets();
await client.rotateSwaggerPortal();Assistant (dashboard) & docs / admin
These match extra routes on the server (not removed from this SDK):
| TneenwhClient method | HTTP |
|------------------------|------|
| assistantStatus() | GET /me/assistant/status |
| assistantChat(body) | POST /me/assistant/chat |
| openApiSpec() | GET /openapi.json |
| docsRoutes() | GET /docs.json |
| adminUsers() | GET /admin/users (admin JWT) |
| adminSessions() | GET /admin/sessions (admin JWT) |
Module tneenwh: assistantStatus, assistantChat, openApiSpec, docsRoutes, adminUsers, adminSessions.
Sessions (panel JWT)
const { items } = await client.sessions.list();
const created = await client.sessions.create('My phone');
await client.sessions.update(sessionId, channelSecret, 'Renamed');
await client.sessions.delete(sessionId, channelSecret);
await client.sessions.disconnect(sessionId, channelSecret);
const { channelSecret } = await client.getSessionChannelSecret(sessionId);Module: sessionsList, sessionCreate, sessionUpdate, sessionDelete, sessionDisconnect, getChannelSecret.
Messaging — SessionApi (client.session)
const ch = client.session(sessionId, channelSecret);| Method | Purpose |
|--------|---------|
| getChannelSecret() | GET …/channel-secret |
| rotateChannelSecret() | POST …/channel-secret/rotate |
| getStatus() | linked / runtime status |
| getDetails() | WhatsApp profile details |
| getQrDataUrl() | QR data URL |
| refreshQr() | New QR when unlinked |
| setWebhook(webhookUrl, events) | POST …/webhook |
| getIncoming() | Recent inbound rows |
| getEvents() | Event log |
| getCalls() | Call notifications |
| downloadInboundMedia(ticket) | Decrypted bytes + content type |
| sendMessage(payload) | Raw ChatSendRequest |
| sendText(toJid, text) | Plain text |
| sendMedia({ to, mimetype, base64, caption?, filename? }) | Media |
| sendList({ to, buttonText, sections, title?, footer?, description? }) | Interactive list |
| sendChatState({ to, state }) | typing | recording | stop — typing indicators |
groups — namespace with: create, get, addParticipants, removeParticipants, promoteParticipants, demoteParticipants, setSubject, setDescription, getInviteCode, revokeInvite, leave, setAddMembersAdminsOnly, setMessagesAdminsOnly, setInfoAdminsOnly, setPicture, deletePicture, getMembershipRequests, approveMembershipRequests, rejectMembershipRequests.
Webhook URL path on your server is commonly /whatsapp/webhook (register in dashboard / POST …/webhook).
Module-style API (tneenwh)
Flat helpers — parity with Python import tneenwh. Call configure({ baseUrl }), setSession(id, secret) for bound calls, setApiKey for /v1 only.
| Key | Maps to |
|-----|---------|
| configure, setBaseUrl, setBearerToken, setApiKey / setApikey | Global client |
| setSession(sessionId, channelSecret) | Default session for bound calls |
| health | client.health() |
| auth.signupStart, auth.signupVerify, auth.login, auth.logout | Auth |
| signupSendOtp, generateOtp, verifyOtp | Aliases |
| me, profile, channelSecrets, rotateSwaggerPortal | Account |
| rotateChannelSecret(sessionId, channelSecret) | One-off rotate |
| openApiSpec, docsRoutes | OpenAPI schema & docs route index |
| sessionsList, sessionCreate, sessionUpdate, sessionDelete, sessionDisconnect, getChannelSecret | Sessions |
| v1SessionsList, v1SendMessage, v1SendList, v1SendChatState | Sub-API |
| session(sessionId, channelSecret) | Returns SessionApi |
| sendMessage, sendText, sendMedia, sendList, sendChatState | Require setSession |
| sessionStatus, sessionDetails, sessionQr, refreshSessionQr, sessionIncoming, sessionEvents, sessionCalls, setWebhook, downloadInboundMedia | Bound session |
| groups | Group helpers (same as SessionApi.groups) |
| formatOtpMessage | OTP text for sendText (not HTTP OTP) |
| advanced | Throws FeatureNotSupportedError on stock server |
Sub-API — V1Channel
Use client.setApiKey(apiKeyFromMe) first.
client.setApiKey(process.env.TNEENWH_API_KEY!);
const rows = await client.v1ListSessions();
const v1 = client.v1Channel(sessionId, channelSecret);
await v1.sendText('[email protected]', 'Hello via v1');
await v1.sendMessage({ to: '[email protected]', message: 'Raw payload' });
await v1.sendChatState({ to: '[email protected]', state: 'typing' });| Method | HTTP |
|--------|------|
| sendMessage(payload) | POST /v1/sessions/:id/messages/send |
| sendText(toJid, text) | Convenience |
| sendMedia({...}) | Convenience |
| sendList({...}) | Convenience |
| sendChatState(payload) | POST /v1/sessions/:id/chat-state |
Groups
Requires a linked session. Prefer ch.groups.* or tneenwh.groups.* after setSession.
await ch.groups.create({ title: 'Team', participants: ['[email protected]'] });
await ch.groups.get('[email protected]');
await ch.groups.addParticipants('[email protected]', ['[email protected]']);
await ch.groups.removeParticipants('[email protected]', ['[email protected]']);
await ch.groups.promoteParticipants('[email protected]', ['[email protected]']);
await ch.groups.demoteParticipants('[email protected]', ['[email protected]']);
await ch.groups.setSubject('[email protected]', 'Title');
await ch.groups.setDescription('[email protected]', 'About');
await ch.groups.getInviteCode('[email protected]');
await ch.groups.revokeInvite('[email protected]');
await ch.groups.leave('[email protected]');
await ch.groups.setAddMembersAdminsOnly('[email protected]', true);
await ch.groups.setMessagesAdminsOnly('[email protected]', true);
await ch.groups.setInfoAdminsOnly('[email protected]', true);
await ch.groups.setPicture('[email protected]', 'image/jpeg', base64);
await ch.groups.deletePicture('[email protected]');
await ch.groups.getMembershipRequests('[email protected]');
await ch.groups.approveMembershipRequests('[email protected]', {});
await ch.groups.rejectMembershipRequests('[email protected]', {});OTP helper (no HTTP)
Builds the OTP message string for sendText (not the signup HTTP OTP):
import { formatOtpNotificationMessage } from 'tneenwh';
const text = formatOtpNotificationMessage({
fromName: 'My App',
receiverId: 'user-1',
otp: '123456',
userName: 'Alex'
});advanced — not on stock REST mapping
advanced.sendList remains a compatibility shim; use session(...).sendList(...), v1Channel(...).sendList(...), or tneenwh.sendList(...).
advanced.setStatus,advanced.channelNewsletteradvanced.group.create/advanced.group.setSubject— usesession().groups/tneenwh.groupsinsteadadvanced.profile.setAbout/setName
Errors
import { TneenwhApiError, FeatureNotSupportedError, isTneenwhApiError } from 'tneenwh';
try {
await ch.sendText('[email protected]', 'Hi');
} catch (e) {
if (e instanceof FeatureNotSupportedError) {
console.error(e.feature);
} else if (isTneenwhApiError(e)) {
console.error(e.status, e.body);
if (e.isUnauthorized()) {
/* refresh JWT */
}
}
}Use TneenwhApiError predicate helpers where available (isUnauthorized, isNotFound, …).
React / Next.js
import { TneenwhProvider, useTneenwh } from 'tneenwh/react';
export function App() {
return (
<TneenwhProvider baseUrl={process.env.NEXT_PUBLIC_TNEENWH_URL!} credentials="include">
<Dashboard />
</TneenwhProvider>
);
}
function Dashboard() {
const client = useTneenwh();
return null;
}Use 'use client' in the file that imports tneenwh/react. With credentials: 'include', cookie auth from the dashboard can pair with same-origin requests.
Utilities
| Export | Purpose |
|--------|---------|
| phoneToWhatsAppJid(e164) | Normalize phone → @c.us JID |
| CHAT_JID_RE | Regex for chat JIDs |
| requestSignupOtp(client, body) | Wrapper around auth.signupStart |
More documentation
| Doc | Content |
|-----|---------|
| Repo docs/TNEENWH-LIBRARY-REFERENCE.md | Parity tables, Python mirror, errors |
| Repo docs/TNEENWH-SDK.md | Short onboarding |
| openapi.json on your host | Canonical schemas |
License
MIT
