Skip to content

API Reference — Conventions

This is the reference section for the FluidTalk Characters API. Every endpoint here is something your bot or connector can call to drive a FluidTalk character — push inbound DMs and get the character's reply, fire conversation triggers, report purchases, pull proactive follow-ups, and generate public comments and threaded replies.

FluidTalk generates the character's messages and tracks the relationship (one consistent, cross-surface-aware persona). It never posts anything itself — reading and sending on Instagram, X, Reddit, Fanvue, TikTok, or any other platform is your connector's job. How you connect to the platform is your concern.

This page documents the conventions that apply to every endpoint — the base URL, the authentication header, the response envelope, identifiers, timestamps, idempotency, the follow-up pull-queue, and the standard error shape. Read it once; each resource page below assumes it.

If you are new here, start with the Quickstart, then Authentication and Core concepts.


Base URL

All Characters API endpoints live under a single versioned prefix:

https://api-talk.fluidvip.com/api/v1/characters

The version segment (/v1) is part of the contract. A future /v2 will be introduced side-by-side without changing /v1, so an integration pinned to /api/v1 keeps working. See Versioning for the stability policy.

Two hosts you will encounter:

HostPurpose
https://api-talk.fluidvip.com/api/v1/charactersThe API you call.
https://talk.fluidvip.comThe FluidTalk dashboard, where you build characters, bind per-platform workflows, and copy a character's connector token (the character's platform settings). An active subscription is required. Tokens cannot be created via the API.

Both SDKs default to the production base URL, so you only supply your connector token:

python
from fluidtalk import FluidTalk

ft = FluidTalk(token="ftc_live_...")
typescript
import { FluidTalk } from "fluidtalk";

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

Authentication

Every request must carry your per-character connector token. Send it in the X-Connector-Token header:

X-Connector-Token: ftc_live_...

The token is the character. There is no account-level key and no character_id field — you address a character by its token and name the platform in each request body. A character can run on several platforms; the same token works for all of them, and the platform you send selects the bound workflow. Both SDKs attach the header for you.

Tokens look like ftc_live_.... Get one from the character's platform settings in the dashboard. It is shown once — treat it like a password. A missing, malformed, or revoked token returns 401 invalid_token.

One token, many surfaces. Because the token authenticates as the character (not as a platform or an account), the same DM funnel, public-comment, trigger, and follow-up endpoints all accept it. The character stays one coherent persona across every surface — see Core concepts.


Requests and responses

Content type

Request bodies are JSON. Send Content-Type: application/json on any call with a body (every POST here). Responses are JSON. A lead is always identified by platform + handle in the body, never by an internal id you have to manage.

The response envelope

Every endpoint wraps its result in the same envelope. On success (HTTP 2xx) the real payload is always under data:

json
{
  "data": { "...": "the endpoint result" },
  "request_id": "req_8f3c2a1d"
}

When a reference page lists a response shape, it is describing the inner data object — the { data, request_id } wrapper is always there. Every response (success or error) also carries an X-Request-Id header whose value matches request_id; quote it when contacting support so we can correlate your call with our logs.

On error (HTTP 4xx/5xx) the body is an error object instead — see Errors below.

Identifiers

There are no UUIDs to manage. A character is addressed by its token; a lead is addressed by platform + handle (the lead's @username), which you pass on every call:

json
{ "platform": "instagram", "handle": "mark" }

Re-using the same (platform, handle) resumes that lead's conversation. A session (one conversation thread) is auto-created and resumed per (character, platform, handle), so you rarely pass session_id. Follow-up records and comment threads are addressed by the ids the API returns (id, thread_id) — pass them back exactly as received; they are opaque.

Timestamps

All timestamps are ISO-8601 in UTC, with a trailing Z:

"created_at": "2026-06-23T14:05:00Z"

Idempotency

Webhook-style endpoints take an external_event_id — your own idempotency key, unique per owner + platform — so a re-delivered webhook applies exactly once:

EndpointIdempotent onRepeat behavior
POST /eventsexternal_event_idReturns { "ok": true, "deduped": true } and does not re-apply the purchase/refund.
POST /triggersexternal_event_idReturns { "ok": true, "deduped": true } and does not re-open or re-stimulate the conversation.
POST /followups/{id}/ackthe follow-up idTerminal and idempotent — acking an already-delivered follow-up returns success with deduped: true.

POST /chat has no message id, so a re-sent inbound creates a new turn. Ensure at-most-once delivery of inbound DMs, or pass a stable session_id to keep re-sends in one conversation. See POST /chat.


The follow-ups pull-queue

GET /followups is a pull-queue: the character periodically queues proactive "hey stranger" rekindles for dormant leads, you pull the pending ones (oldest first), send each on the platform, then ack each one to move it from PENDING to DELIVERED.

Query parameterMeaning
platformRequired for character-token callers — which platform's queue to pull.
own_usernameOptional. Multi-account dedup: a follow-up claimed by a specific account is delivered only to that account.
limitMaximum number to return in one pull. Default 100, max 500.

There is no cursor — the queue drains as you ack. Pull, deliver, ack, repeat. See GET /followups.


Response semantics

These rules hold across endpoints. Relying on them keeps your integration correct when a conversation has ended, when multi-account dedup suppresses an inbound, and when an event or surface is not configured.

Replies are a list of bubbles

A character's reply is a list of short chat bubbles with human-like delays, not one blob. Send them in order, honoring each delay_ms:

json
{
  "session_id": "...",
  "bubbles": [
    { "text": "heyy", "delay_ms": 0, "image_url": null },
    { "text": "how was your day??", "delay_ms": 1400, "image_url": null }
  ]
}

Closed conversations return no bubbles

If a conversation is already closed, bubbles is [] — there is nothing to send. Treat an empty bubble list as "say nothing", not as an error.

Multi-account dedup may ignore an inbound

When a character runs several accounts on one platform and dedup is enabled, an inbound from an account that did not claim the lead is ignored:

json
{ "session_id": null, "bubbles": [], "ignored": true, "ignore_reason": "lead_claimed_by_other_account" }

Send nothing when ignored is true. See Multi-account dedup.

A paused character sends nothing

A character can be paused — most commonly when the account holds more characters than its current plan allows (after a downgrade), the newest characters over the limit pause until the plan is upgraded again. Nothing is deleted, and the character resumes automatically on upgrade. A paused character accepts the request but replies with nothing:

json
{ "session_id": null, "bubbles": [], "paused": true, "pause_reason": "plan_limit" }

Send nothing when paused is true. The same paused / pause_reason pair appears on chat, triggers, and comments; a paused character also returns no follow-ups.

Unconfigured events and surfaces are safe no-ops

Firing a trigger whose event_id is not configured, or asking for a comment on a character/platform where comments are not enabled, returns a benign acknowledgement rather than an error — for example { "ok": true, "ignored": "event not configured" } or { "ok": true, "ignored": "comments not configured" }. An unknown event_id is a safe no-op.


Errors

Every error is an HTTP status paired with a JSON body whose top-level key is error:

json
{
  "error": {
    "code": "invalid_request",
    "message": "platform is required",
    "type": "client_error",
    "request_id": "req_8f3c2a1d"
  }
}

type is client_error for 4xx and server_error for 5xx. The request_id matches the X-Request-Id header. Branch on the HTTP status (or the matching error.code); treat message as human-readable and do not pattern-match its exact text.

Statuserror.codetypeMeaning
400invalid_requestclient_errorBad input — a malformed or missing field.
401invalid_tokenclient_errorMissing, invalid, or revoked connector token. Check the X-Connector-Token header.
402payment_requiredclient_errorThe character owner's wallet can't cover the turn. Returned before any model call. See Billing.
403forbiddenclient_errorAuthenticated, but not allowed to do this.
404not_foundclient_errorNo such resource for this token.
409conflictclient_errorThe request conflicts with current state.
422validation_errorclient_errorBody is well-formed JSON but a field failed validation.
429rate_limitedclient_errorToo many requests — back off and retry.
500internal_errorserver_errorSomething failed on our side — retry with backoff.

The SDKs map these to typed errors: AuthError (401), PaymentRequiredError (402), PermissionError (403), NotFoundError (404), ConflictError (409), ValidationError (422), RateLimitError (429), and ApiError for anything else. Full payload shapes and handling patterns are on the Errors page.


Reference pages

The Characters API surface, by capability:

PageWhat it covers
ChatPOST /chat — the lead DM'd the character; get the character's reply as a list of bubbles.
EventsPOST /events — report a purchase, refund, or chargeback; the API flips the lead's lifecycle.
TriggersPOST /triggers — fire an entry event (story reaction, new follower, custom); the character opens or advances the conversation.
Follow-upsGET /followups and POST /followups/{id}/ack — pull queued re-engagement messages and acknowledge delivery.
CommentsPOST /comments and POST /comments/reply — generate a public comment for a post and threaded replies under it.
MediaPOST /inbound-media — upload the bytes of a lead-sent image and get a permanent URL for /chat's image_url.

For the conceptual model behind these endpoints (character → platform + handle → session → Person, with cross-surface awareness), see Core concepts. For the SDK method names that map onto each endpoint, see the Python SDK and TypeScript SDK references.


Building characters, binding per-platform workflows, configuring entry points and multi-account dedup, and copying a connector token are done in the FluidTalk dashboard (https://talk.fluidvip.com), not through the API. The API drives a character; it does not create one.

FluidTalk Characters API — part of the Fluidvip ecosystem.