@fluidtalk/sdk
v1.0.3
Published
Official TypeScript/JavaScript client for the FluidTalk Handshake API. Drop FluidTalk's AI persona replies into your own chatbot backend without rebuilding the API client.
Downloads
534
Maintainers
Readme
@fluidtalk/sdk
Official TypeScript/JavaScript client for the FluidTalk Handshake API. Drop FluidTalk's AI persona replies into your own chatbot backend without rebuilding the HTTP/auth/multipart layer.
You keep your own orchestration (when to open a chat, when to follow up, where to send messages); this client just gives you the persona's reply in one call.
npm install @fluidtalk/sdk⚠️ Server-side only. Use this from your backend. Never put your
ft_sk_key in frontend, browser, or mobile-client code — it would ship in your bundle and leak. Your server calls FluidTalk; your client calls your server.
import { FluidTalk } from "@fluidtalk/sdk";
const ft = new FluidTalk({
apiKey: process.env.FT_API_KEY!, // ft_sk_... (scoped to one persona + engine)
ownUsername: "@my_account", // optional default for tracking / dedup
// baseUrl: "https://api-talk.fluidvip.com" (default)
});
// A lead messaged you → get the persona's reply and relay it on your platform
const res = await ft.inboundChat({ target: "@john_doe", message: "hey, saw your post!" });
for (const bubble of res.replies) await myChat.send("@john_doe", bubble);
if (res.shouldSendPhoto && res.photoUrl) await myChat.sendImage("@john_doe", res.photoUrl);Concepts → methods
A conversation can start four ways. The builder names map to these methods (the API wire value is handled for you):
| Builder name | When | Method |
| :-- | :-- | :-- |
| Inbound Chat | the lead messages first (or an ongoing chat) | ft.inboundChat({ target, message }) |
| AI Opener | your persona messages first | ft.aiOpener({ target }) |
| Event Trigger | a platform event fires | ft.eventTrigger({ target, eventType, context }) |
| Follow-up | re-engage a quiet lead | ft.followUp({ target }) |
| — | host a local image for context | ft.uploadImage({ path }) |
A conversation is keyed by (persona, target) — reuse the same target for the same lead and the engine keeps phase/state across turns. The persona + engine are fixed by your API key.
An image (profile screenshot / context photo) is accepted by inboundChat, aiOpener, and eventTrigger — not followUp.
Examples
// AI opener to a new lead (optionally with a profile screenshot for better targeting)
const opener = await ft.aiOpener({ target: "@lead", image: { path: "./profile.jpg" } });
await myChat.send("@lead", opener.message);
// Platform event (eventType must match an Event Trigger configured in your engine)
const ev = await ft.eventTrigger({ target: "@lead", eventType: "story_reaction", context: { reaction: "🔥" } });
// Re-engage a ghost (needs an existing conversation)
const fu = await ft.followUp({ target: "@lead" });
// Attach an image the lead sent — from a local file path...
const { url } = await ft.uploadImage({ path: "./incoming.jpg" });
const r = await ft.inboundChat({ target: "@lead", message: "what do you think?", image: { url } });
// ...or from bytes (e.g. a browser upload relayed to your backend, or serverless)
const up = await ft.uploadImage({ data: bytes, filename: "lead.jpg" });
// you can also pass raw bytes straight to a message: image: { data: bytes, filename: "lead.jpg" }Results
inboundChat→{ reply, replies[], shouldSendPhoto, photoUrl, sessionId, ignored, ignoreReason }aiOpener/eventTrigger/followUp→{ sessionId, message, ignored, ignoreReason, continued, continuedReason }uploadImage→{ url }
Always send replies as separate bubbles; when shouldSendPhoto is true, also send photoUrl.
Behaviour to handle
continued: true— the lead already had an active conversation, so the action continued it instead of starting fresh. For an opener (aiOpener),messageis then""— send nothing.continuedReasontells you why (e.g."conversation_already_active"). ForeventTrigger, the event is folded into the ongoing chat andmessageis the reply.ignored: true— duplicate lead (already owned by another account of the persona). Send nothing.- Sealed conversation. When a conversation is finished, the next
inboundChatthrowsFluidTalkErrorwithstatus: 400("This session is DONE") — stop messaging that lead.
import { FluidTalk, FluidTalkError } from "@fluidtalk/sdk";
try {
const res = await ft.inboundChat({ target, message });
if (res.ignored) return; // skip duplicate
// ... send res.replies
} catch (e) {
if (e instanceof FluidTalkError) {
if (e.status === 401) { /* missing or invalid API key */ }
else if (e.status === 402) { /* insufficient credits to start a new conversation — see your plan */ }
else if (e.status === 400) { /* conversation already done */ }
else if (e.status === 404) { /* no conversation yet (follow-up before any contact) */ }
}
}API keys are action-only: send messages and upload images, but never change personas/engines/billing (that stays in the dashboard).
