Appearance
API Reference — Triggers
Fire a conversation trigger so a character reacts to something that happened on the platform — a story reaction, a new follower, or any custom entry event you configured. A trigger either opens a brand-new conversation (when the lead has no live chat) or feeds the event into an existing one as the next turn's stimulus.
All routes are under the production base URL:
https://api-talk.fluidvip.com/api/v1/charactersThis endpoint generates the character's opener (or folds the event into a running chat) and returns the message bubbles for you to send. It never reads or posts on the platform itself — how you connect to the platform is your concern. To handle an inbound DM the lead sent you, see Chat; to report that money moved, see Events.
Authentication. Every request carries your per-character connector token in the
X-Connector-Tokenheader. The token is the character — there is no account key and nocharacter_id; you name theplatformin the body and the token selects that platform's bound workflow. See Authentication.
Entry points, cold vs warm
A trigger maps to an entry point you configured in the dashboard for this character and platform — keyed by event_id (for example outreach, story_reaction, new_follower, or a custom key). The trigger's effect depends on whether the lead (platform, handle) already has a live conversation:
- Cold — no live chat exists. The character opens the conversation, reacting to the event. The response carries
opened: trueand the openerbubblesto send. - Warm — a chat already exists. The event becomes the next turn's stimulus: the character weaves it into the conversation rather than starting a new one. The response carries
opened: false.
Fail-closed on unknown events
An event_id that isn't configured for this character/platform is a safe no-op — it never errors and never opens the wrong conversation. The call returns 200 with { "ok": true, "ignored": "event not configured" }, so a stray or misrouted webhook can't accidentally make the character speak.
Idempotency
Triggers are idempotent on external_event_id (unique per owner + platform). A re-delivered webhook applies exactly once; repeats return { "ok": true, "deduped": true } and do not re-open or re-stimulate the conversation. Always pass a stable external_event_id from your platform's event. See Concepts.
POST /triggers
Fire an entry event for a lead. Opens a cold conversation, or applies the event to a warm one.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
platform | string | Yes | The platform the event happened on, e.g. instagram. Selects the character's bound workflow. |
handle | string | Yes | The lead's @username. Together with platform it identifies the lead (and resumes their conversation if one exists). |
event_id | string | Yes | The configured entry-point key, e.g. story_reaction. An unknown key is a safe no-op (see fail-closed). |
external_event_id | string | Yes | Your idempotency key for this event (unique per owner + platform). A repeat is deduped. |
context | string | object | No | A short detail for the opener — e.g. the emoji a lead reacted with ("🔥") or a label describing the event. |
own_username | string | No | The bot account that received this event, for multi-account dedup. Omit when dedup is off. |
raw | object | No | Connector echo for your own logs. Stored as provided; any token-like values are stripped. |
Response
Every response is wrapped in the standard envelope — the real payload is under data, and an X-Request-Id header mirrors request_id. The data shape depends on the outcome.
Cold start — the character opened the conversation:
json
{
"data": {
"ok": true,
"deduped": false,
"opened": true,
"session_id": "ses_3f9a1c2d",
"bubbles": [
{ "text": "omg thank you for the love on my story 🥹", "delay_ms": 0, "image_url": null },
{ "text": "how's your day going?", "delay_ms": 1400, "image_url": null }
]
},
"request_id": "req_8a2c4e7f"
}Warm — the event was applied to an existing chat as the next turn's stimulus:
json
{
"data": {
"ok": true,
"deduped": false,
"opened": false,
"session_id": "ses_3f9a1c2d",
"bubbles": []
},
"request_id": "req_91b0d3aa"
}| Field | Type | Description |
|---|---|---|
ok | boolean | Always true on a 2xx. |
deduped | boolean | true if this external_event_id was already processed — the event was not re-applied. |
opened | boolean | true if the trigger opened a new conversation (cold); false if it was folded into an existing one (warm). |
session_id | string | The conversation this trigger belongs to. |
bubbles | array | The opener message(s) to send, oldest first — present when the character opens. Each bubble: { text, delay_ms, image_url }. Send them in order, honoring delay_ms. |
Other outcomes
The data object is shaped differently when the event is ignored:
- Event not configured —
{ "ok": true, "ignored": "event not configured" }. Unknown/unconfiguredevent_id; send nothing. - Duplicate event —
{ "ok": true, "deduped": true }. Thisexternal_event_idwas already handled; send nothing. - Dedup ignored —
{ "ok": true, "ignored": true }. With multi-account dedup on, the lead is claimed by a different account than theown_usernameyou passed; send nothing. - Paused —
{ "ok": true, "paused": true, "pause_reason": "plan_limit" }. The character is paused (e.g. the account is over its plan's character limit after a downgrade); it won't open or advance a conversation until the plan is upgraded. Send nothing. See Paused characters.
Treat a truthy
ignored(string ortrue) as "do nothing." Only act onbubbleswhenopenedistrue.
Examples
bash
curl -X POST "https://api-talk.fluidvip.com/api/v1/characters/triggers" \
-H "X-Connector-Token: ftc_live_..." \
-H "Content-Type: application/json" \
-d '{
"platform": "instagram",
"handle": "mark",
"event_id": "story_reaction",
"external_event_id": "ig_evt_88212",
"context": "🔥"
}'python
from fluidtalk import FluidTalk
ft = FluidTalk(token="ftc_live_...")
res = ft.trigger(
platform="instagram",
handle="mark",
event_id="story_reaction",
external_event_id="ig_evt_88212",
context="🔥",
)
# Only send when the character opened the conversation (cold start)
if res.opened:
for bubble in res.bubbles:
send_dm("mark", bubble.text) # your platform I/Otypescript
import { FluidTalk } from "fluidtalk";
const ft = new FluidTalk({ token: "ftc_live_..." });
const res = await ft.trigger({
platform: "instagram",
handle: "mark",
eventId: "story_reaction",
externalEventId: "ig_evt_88212",
context: "🔥",
});
// Only send when the character opened the conversation (cold start)
if (res.opened) {
for (const bubble of res.bubbles) {
await sendDm("mark", bubble.text); // your platform I/O
}
}Errors
Errors use the standard envelope: { "error": { "code", "message", "type", "request_id" } }.
| Status | code | When |
|---|---|---|
400 | invalid_request | Malformed body or a missing required field (platform, handle, event_id, external_event_id). |
401 | invalid_token | Missing, invalid, or revoked connector token. |
402 | payment_required | The character owner's wallet can't cover a cold opener. Charged before any model call, so a refused trigger costs nothing — top up the wallet. See Billing. |
403 | forbidden | The token isn't permitted for this platform or action. |
404 | not_found | The addressed resource doesn't exist for this token. |
409 | conflict | The request conflicts with the current state of the conversation. |
422 | validation_error | The body failed validation (e.g. a malformed field value). |
429 | rate_limited | Too many requests; retry after the Retry-After header. |
500 | internal_error | Unexpected server error; safe to retry the same external_event_id. |
An unknown
event_idis not an error — it returns200withignored: "event not configured"(fail-closed).
The SDKs raise typed errors (AuthError, PaymentRequiredError, PermissionError, NotFoundError, ConflictError, ValidationError, RateLimitError, ApiError) — see Python and TypeScript. Full payload shapes are on the Errors page.
Related
- Chat — handle an inbound DM and get the character's reply.
- Events — report a purchase, refund, or chargeback.
- Follow-ups — pull proactive re-engagement messages the character queues.
- Multi-account dedup —
own_usernameand the claimed-leadignoredresponse. - Core concepts — entry points, sessions, and cross-surface awareness.
- API Reference index