Skip to content

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/characters

These 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-Token header. The token is the character — there is no account-level key and no character_id field; you name the platform in 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 an X-Request-Id header 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 inner data unless a full envelope is given. See Errors.


The Followup object

Each pending follow-up returned by GET /followups is one Followup object.

FieldTypeDescription
idstringThe follow-up id. Pass it to POST /followups/{id}/ack after you deliver the message.
handlestringThe lead's @username to send the message to (paired with platform).
platformstringThe platform this follow-up is for, e.g. instagram.
kindstringThe follow-up category, e.g. rekindle (a dormant-lead re-engagement).
messagestringThe exact message text the character wants you to send.
created_atstring (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 PENDING and keeps coming back on every pull until you ack it. Send the message on 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

NameTypeRequiredDescription
platformstringYesThe platform to pull follow-ups for. Required for connector-token callers (the token already identifies the character).
own_usernamestringNoMulti-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.
limitintegerNoMax 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_username so 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 (no own_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

NameTypeRequiredDescription
platformstringYesThe platform to queue follow-ups for.
idle_hoursnumberNoHow long a lead must have been silent to count as dormant. Defaults to the character's configured dormant window. Must be greater than 0.
limitintegerNoMax 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, and CHURNED leads 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 paramTypeDescription
idstringThe 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"
}
FieldTypeDescription
okbooleanAlways true on success.
dedupedbooleantrue if this follow-up was already DELIVERED (a repeat ack); false on the first ack.
delivered_atstring (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

Statuserror.codeWhen
400invalid_requestBad input — e.g. platform omitted on a connector-token pull.
401invalid_tokenMissing, invalid, or revoked connector token.
403forbiddenThe token isn't allowed to read or ack this queue.
404not_foundack only — unknown follow-up id, or it doesn't belong to this character.
422validation_errorValidation — e.g. limit out of the 1500 range.
429rate_limitedRate limited; retry after the Retry-After header. See Rate limits.
500internal_errorUnexpected 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.


  • 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

FluidTalk Characters API — part of the Fluidvip ecosystem.