Skip to content

Proactive follow-ups ​

A FluidTalk character doesn't only react — it reaches back out. When a lead goes quiet, the character periodically queues a rekindle ("hey stranger…") to re-open the conversation. FluidTalk generates the message and holds it in a pull-queue; your connector pulls the pending follow-ups, sends each one on the platform, and acks it. The API never sends anything itself — how you reach the lead on Instagram is your concern.

This page covers the full loop: pulling pending follow-ups with GET /followups, delivering them, and acknowledging each with POST /followups/{id}/ack.

  • Base URL: https://api-talk.fluidvip.com/api/v1/characters
  • Authentication: X-Connector-Token: ftc_... (the token is the character — see Concepts)

The follow-up lifecycle ​

A follow-up is a single queued message bound to one lead — (platform, handle) — that moves through exactly two states:

StateWhat it means
PENDINGThe character has generated a rekindle and it's waiting for you to pull and deliver it. This is what GET /followups returns, for 24 hours: a follow-up nobody delivered within a day is outdated and is no longer returned.
DELIVEREDYou've sent it on the platform and called ack. This is terminal — a delivered follow-up never re-appears in the queue.

You don't create follow-ups; the character queues them on its own cadence for leads that have gone dormant (with per-character caps and de-duplication so a lead is never spammed). Your job is the delivery half of the loop: pull → send → ack.

Generating a rekindle is a metered model call charged to the character owner's wallet — see Billing. Pulling and acking follow-ups are free; you are only billed when the character writes the message, not when you fetch it.

Pull pending follow-ups ​

GET /followups returns this connector's PENDING follow-ups, oldest first, so you can deliver them in the order the character queued them.

Query parameters ​

NameTypeRequiredDescription
platformstringYesThe platform to pull for (e.g. instagram). Required for a character-token caller.
own_usernamestringNoThe bot account doing the pull, for multi-account dedup. A follow-up claimed by a specific account is delivered only to that account.
limitintegerNoMax rows to return. Default 100, max 500.

Response shape ​

The follow-ups live under data.followups. As with every endpoint, the payload is wrapped in the { data, request_id } envelope and the same id is echoed in the X-Request-Id header.

json
{
  "data": {
    "followups": [
      {
        "id": "fu_3f2504e0",
        "handle": "mark",
        "platform": "instagram",
        "kind": "rekindle",
        "message": "hey stranger, you kinda vanished on me 👀 how've you been?",
        "created_at": "2026-06-26T09:12:00Z"
      }
    ]
  },
  "request_id": "req_8f3c2a10"
}
FieldTypeDescription
idstringThe follow-up id. Pass it to ack once delivered.
handlestringThe lead's @username to send the message to.
platformstringThe platform the follow-up is for.
kindstringThe follow-up category (currently rekindle). Treat it as an opaque label for routing/analytics — don't branch behavior on an unknown value.
messagestringThe text to send, exactly as written. Send it verbatim.
created_atstringISO 8601 timestamp of when the follow-up was queued.

An empty queue returns { "data": { "followups": [] }, "request_id": "req_..." } — there is simply nothing dormant to rekindle right now.

Examples ​

bash

bash
curl "https://api-talk.fluidvip.com/api/v1/characters/followups?platform=instagram" \
  -H "X-Connector-Token: ftc_..."

Python (pip install fluidtalk)

python
from fluidtalk import FluidTalk

ft = FluidTalk(token="ftc_8f3c...")

pending = ft.followups.list(platform="instagram")
for fu in pending.followups:
    print(fu.id, fu.handle, fu.message)

TypeScript (npm install fluidtalk)

typescript
import { FluidTalk } from "fluidtalk";

const ft = new FluidTalk({ token: "ftc_8f3c..." });

const { followups } = await ft.followups.list({ platform: "instagram" });
for (const fu of followups) {
  console.log(fu.id, fu.handle, fu.message);
}

Errors ​

HTTPerror.codeWhen
401invalid_tokenMissing, invalid, or revoked connector token.
422validation_errorplatform is missing (required for a character token).
429rate_limitedToo many requests — honor Retry-After and back off.
500internal_errorServer-side failure; retry with backoff.

The SDKs raise typed errors — AuthError (401), ValidationError (422), RateLimitError (429), and ApiError for the rest. See Errors.

Acknowledge a delivered follow-up ​

POST /followups/{id}/ack moves a follow-up from PENDING to DELIVERED. Call it after you've sent the message on the platform, so a row stays in the queue until it has actually gone out.

The underlying path is /followups/{id}/delivered; the SDKs and these docs call it ack for short.

ack is idempotent: acking the same id twice is safe and returns deduped: true on repeats without changing the delivery time. This makes the loop crash-safe — if your worker dies between sending and acking, a re-run can ack again without harm.

Response shape ​

json
{
  "data": { "ok": true, "deduped": false, "delivered_at": "2026-06-26T09:14:05Z" },
  "request_id": "req_b1c4d2e8"
}
FieldTypeDescription
okbooleanAlways true on success.
dedupedbooleantrue if this id was already DELIVERED — the ack was a no-op.
delivered_atstringISO 8601 timestamp of the original delivery.

Examples ​

bash

bash
curl -X POST \
  https://api-talk.fluidvip.com/api/v1/characters/followups/fu_3f2504e0/ack \
  -H "X-Connector-Token: ftc_..."

Python (pip install fluidtalk)

python
result = ft.followups.ack("fu_3f2504e0")
print(result.delivered_at, result.deduped)

TypeScript (npm install fluidtalk)

typescript
const result = await ft.followups.ack("fu_3f2504e0");
console.log(result.delivered_at, result.deduped);

Errors ​

HTTPerror.codeWhen
401invalid_tokenMissing, invalid, or revoked connector token.
404not_foundUnknown follow-up id, or one that isn't this connector's. Ids never leak across connectors — a foreign id is a 404, never a 403.
429rate_limitedToo many requests — honor Retry-After.
500internal_errorServer-side failure; retry with backoff.

The SDKs map these to AuthError (401), NotFoundError (404), RateLimitError (429), and ApiError for the rest. See Errors.

The full loop ​

Pull, deliver on the platform, then ack — one tight loop you run on a schedule.

Python

python
from fluidtalk import FluidTalk

ft = FluidTalk(token="ftc_8f3c...")

pending = ft.followups.list(platform="instagram", limit=100)
for fu in pending.followups:
    send_dm_on_instagram(fu.handle, fu.message)  # your platform I/O
    ft.followups.ack(fu.id)                       # mark it delivered

TypeScript

typescript
import { FluidTalk } from "fluidtalk";

const ft = new FluidTalk({ token: "ftc_8f3c..." });

const { followups } = await ft.followups.list({
  platform: "instagram",
  limit: 100,
});

for (const fu of followups) {
  await sendDmOnInstagram(fu.handle, fu.message); // your platform I/O
  await ft.followups.ack(fu.id); // mark it delivered
}

Ack after the send succeeds, not before. If a send fails, skip the ack — the follow-up stays PENDING and you'll pull it again next cycle. Because ack is idempotent, retrying the ack after a partial failure is always safe.

Polling cadence ​

The queue is a pull model — FluidTalk never pushes follow-ups to you, so you decide how often to drain it. A poll every few minutes is plenty for rekindles, which are not time-critical; there's no benefit to polling tighter than the character queues new work. Each GET /followups counts against your per-token request budget, so keep the cadence sensible — see Rate limits. A single pull returns up to limit rows (max 500), so on a busy character, keep pulling until you get a short page rather than polling faster.

Multi-account delivery (own_username) ​

If the character runs several accounts on one platform, pass own_username so each account only drains its own follow-ups. A follow-up for a lead claimed by one account is delivered only to that account's pulls — another account asking with a different own_username won't see it, so no lead gets messaged twice. Omit own_username and the queue behaves normally (single account). Full rules in Multi-account dedup.

bash
curl "https://api-talk.fluidvip.com/api/v1/characters/followups?platform=instagram&own_username=ava.daily&limit=50" \
  -H "X-Connector-Token: ftc_..."

Patterns ​

  • Drain in a worker. Run the pull → send → ack loop on a timer (every few minutes). Keep paging until a pull returns fewer than limit rows.
  • Ack last, idempotently. Send first, ack second. A crash between the two just re-queues the row; re-acking a delivered id is a safe no-op (deduped: true).
  • Respect the lead's reply. A rekindle re-opens the conversation — when the lead answers, route their reply straight back through POST /chat; the character resumes the same session and stays cross-surface aware.
  • One queue per account. Running multiple accounts? Always pass the matching own_username on the pull so each account delivers only its claimed leads.

FluidTalk Characters API — part of the Fluidvip ecosystem.