Appearance
API Reference — Follow-ups
Operations for working with the character's proactive follow-up queue: pull the pending re-engagement messages this connector should deliver, then acknowledge each one once you've sent it on the platform.
All routes are under the production base URL:
https://api-talk.fluidvip.com/api/v1/charactersThese endpoints expose a pull-queue. You ask the character to queue proactive "hey stranger" follow-ups for leads who have gone quiet (POST /followups/sweep), pull what's pending, send each one on the platform yourself, then ack so they aren't handed out again. Ask → pull → send → ack, on whatever cadence suits your platform. The API generates the message text and tracks the relationship — it never sends anything itself. How you deliver the message on Instagram, X, Reddit, etc. is your concern. To drive live DMs, see Chat; to start a conversation from an event, see Triggers.
Authentication. Send your per-character connector token in the
X-Connector-Tokenheader. The token is the character — there is no account-level key and nocharacter_idfield; you name theplatformin each request. The same token works for every platform the character runs on. See Authentication.
Envelope. Every response is wrapped as
{ "data": <result>, "request_id": "req_..." }and carries anX-Request-Idheader with the same value (use it for support and log correlation). Errors come back as{ "error": { "code", "message", "type", "request_id" } }. The shapes below show the innerdataunless a full envelope is given. See Errors.
The Followup object
Each pending follow-up returned by GET /followups is one Followup object.
| Field | Type | Description |
|---|---|---|
id | string | The follow-up id. Pass it to POST /followups/{id}/ack after you deliver the message. |
handle | string | The lead's @username to send the message to (paired with platform). |
platform | string | The platform this follow-up is for, e.g. instagram. |
kind | string | The follow-up category, e.g. rekindle (a dormant-lead re-engagement). |
message | string | The exact message text the character wants you to send. |
created_at | string (ISO-8601 UTC) | When the follow-up was queued. The queue is returned oldest first. |
Example:
json
{
"id": "fu_3a1b9c7d2e",
"handle": "mark",
"platform": "instagram",
"kind": "rekindle",
"message": "hey stranger, you kinda disappeared on me 👀 how've you been?",
"created_at": "2026-06-26T09:14:02Z"
}Deliver, then ack. A follow-up stays
PENDINGand keeps coming back on every pull until you ack it. Send themessageon the platform first, then call ack so the same "hey stranger" is never sent twice. Acking is the only way a follow-up leaves the queue.
GET /followups
Pull this connector's pending proactive follow-ups, oldest first.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
platform | string | Yes | The platform to pull follow-ups for. Required for connector-token callers (the token already identifies the character). |
own_username | string | No | Multi-account dedup: the bot account doing the pulling. A follow-up claimed by a specific account is delivered only to that account. Omit when the character runs a single account. See Multi-account dedup. |
limit | integer | No | Max rows to return. Default 100, max 500. Out-of-range values return 422. |
Returns { followups: [Followup, ...] }. An empty queue returns { "followups": [] } — poll on a schedule that suits your platform. A paused character (e.g. the account is over its plan's character limit after a downgrade) also returns { "followups": [] } — it queues no proactive follow-ups until the plan is upgraded. See Paused characters.
Response
json
{
"data": {
"followups": [
{
"id": "fu_3a1b9c7d2e",
"handle": "mark",
"platform": "instagram",
"kind": "rekindle",
"message": "hey stranger, you kinda disappeared on me 👀 how've you been?",
"created_at": "2026-06-26T09:14:02Z"
}
]
},
"request_id": "req_7f2c9a01b4"
}Multi-account dedup. When the character runs several accounts on one platform (the "mother-slave" setup), pass
own_usernameso each account only pulls the follow-ups assigned to it — two accounts will never send the same lead the same "hey stranger". With dedup off (noown_username), every pending follow-up for the platform is returned. See Multi-account dedup.
Examples
bash
curl "https://api-talk.fluidvip.com/api/v1/characters/followups?platform=instagram&limit=100" \
-H "X-Connector-Token: ftc_live_8f3c..."python
from fluidtalk import FluidTalk
ft = FluidTalk(token="ftc_live_8f3c...")
# Pull this connector's pending follow-ups (oldest first)
pending = ft.followups.list(platform="instagram")
for f in pending.followups:
# ...you send the message on the platform yourself...
send_dm(f.handle, f.message)
# ...then ack so it isn't handed out again
ft.followups.ack(f.id)typescript
import { FluidTalk } from "fluidtalk";
const ft = new FluidTalk({ token: "ftc_live_8f3c..." });
// Pull this connector's pending follow-ups (oldest first)
const pending = await ft.followups.list({ platform: "instagram" });
for (const f of pending.followups) {
// ...you send the message on the platform yourself...
await sendDm(f.handle, f.message);
// ...then ack so it isn't handed out again
await ft.followups.ack(f.id);
}POST /followups/sweep
Ask the character to queue follow-ups now for the leads of this connector that have gone quiet, then pull them with GET /followups.
The queue does not fill itself on your schedule — a re-engagement pass has to run for a pending follow-up to exist. This endpoint runs that pass on demand, scoped to your connector, so you control the cadence: your bot knows how long is "too quiet" on your platform (a Telegram DM goes cold far sooner than an Instagram thread). Call it right before each pull.
Body
| Name | Type | Required | Description |
|---|---|---|---|
platform | string | Yes | The platform to queue follow-ups for. |
idle_hours | number | No | How long a lead must have been silent to count as dormant. Defaults to the character's configured dormant window. Must be greater than 0. |
limit | integer | No | Max follow-ups to queue in this call. Default 3. Each queued follow-up is a generation, so keep this small and call more often. |
Returns { queued: <integer> } — how many new follow-ups were queued. A paused character returns { "queued": 0, "paused": true, "pause_reason": "plan_limit" }.
Safe to call every cycle. The per-lead caps are enforced here, not by you: at most one follow-up per lead per UTC day, at most 3 in any 90 days, and never within 24 hours of the last one. A lead who isn't due yet simply isn't queued, so a sweep on every poll returns
{ "queued": 0 }and costs nothing. Leads are also skipped while they're within the post-sale quiet period, andCHURNEDleads are never re-engaged.
Response
json
{
"data": { "queued": 1 },
"request_id": "req_5c1e77b902"
}Examples
bash
curl -X POST "https://api-talk.fluidvip.com/api/v1/characters/followups/sweep" \
-H "X-Connector-Token: ftc_live_8f3c..." \
-H "Content-Type: application/json" \
-d '{"platform":"telegram","idle_hours":12,"limit":3}'python
# ask, then pull — the whole proactive loop
ft.followups.sweep(platform="telegram", idle_hours=12)
for f in ft.followups.list(platform="telegram").followups:
send_dm(f.handle, f.message)
ft.followups.ack(f.id)POST /followups/{id}/ack
Acknowledge that a pulled follow-up was delivered on the platform. This moves it PENDING → DELIVERED, which is terminal — the follow-up is removed from the pull-queue and never handed out again.
Path note. The underlying route is
/followups/{id}/delivered; the SDKs and these docs call it ack. Both refer to the same operation.
| Path param | Type | Description |
|---|---|---|
id | string | The follow-up id from GET /followups. |
The call is idempotent: acking an already-delivered follow-up succeeds and returns deduped: true without changing anything.
Response
json
{
"data": { "ok": true, "deduped": false, "delivered_at": "2026-06-26T09:20:11Z" },
"request_id": "req_88de1f3ac0"
}| Field | Type | Description |
|---|---|---|
ok | boolean | Always true on success. |
deduped | boolean | true if this follow-up was already DELIVERED (a repeat ack); false on the first ack. |
delivered_at | string (ISO-8601 UTC) | When the follow-up was marked delivered. |
Examples
bash
curl -X POST "https://api-talk.fluidvip.com/api/v1/characters/followups/fu_3a1b9c7d2e/ack" \
-H "X-Connector-Token: ftc_live_8f3c..."python
ack = ft.followups.ack("fu_3a1b9c7d2e")
print(ack.deduped, ack.delivered_at)typescript
const ack = await ft.followups.ack("fu_3a1b9c7d2e");
console.log(ack.deduped, ack.delivered_at);Errors
| Status | error.code | When |
|---|---|---|
400 | invalid_request | Bad input — e.g. platform omitted on a connector-token pull. |
401 | invalid_token | Missing, invalid, or revoked connector token. |
403 | forbidden | The token isn't allowed to read or ack this queue. |
404 | not_found | ack only — unknown follow-up id, or it doesn't belong to this character. |
422 | validation_error | Validation — e.g. limit out of the 1–500 range. |
429 | rate_limited | Rate limited; retry after the Retry-After header. See Rate limits. |
500 | internal_error | Unexpected server error (type: "server_error"); safe to retry. |
type is client_error for 4xx and server_error for 5xx. See Errors for the full error model. The SDKs raise typed errors (AuthError, PaymentRequiredError, PermissionError, NotFoundError, ConflictError, ValidationError, RateLimitError, ApiError) — see Python and TypeScript.
Related
- Chat — push an inbound DM and get the character's reply.
- Triggers — fire an entry event (story reaction, new follower, custom) to open a conversation.
- Multi-account dedup — run several accounts on one platform without double-sending.
- Concepts — sessions, people, and cross-surface awareness.
- API Reference index