Skip to content
ChatfuelSDK
ReferenceChatfuel API

Traps

The cross-domain sharp edges of the Chatfuel API — identity and selection, schema and introspection, withdrawn names, pagination, and data semantics — in full.

These bite in every domain, and none of them is visible from a type signature alone.

Identity and selection

TrapWhat to do
Conversation.id is the contact id.Every conversationID argument takes a contact id; there is no separate conversation identity, and passing one is not a bug to fix.
__typename is not optional on Contact and Conversation.Both are interfaces with several implementers, so the branch you take depends on a field you have to ask for. Select it on both — and, as cheap insurance, on every interface and union selection you write.
Interface field names are disambiguated per platform.One field name must map to one type across every implementer, so the same concept gets different names: whatsappStatus on a WhatsApp message against plain status on a widget one, and waReferral / fbReferral / igReferral for the referral. Never assume a field from one platform's message type exists on another's.
__typename in a mutation's variables is rejected.The server refuses unknown input fields. If you round-trip a fetched object into a mutation input, strip __typename recursively first.

A message's clientId must be a fresh UUID per message and unique across every client of the account, your app and the Chatfuel dashboard included — both merge messages by clientId, so a collision corrupts your UI and the customer's dashboard at the same time, with no error anywhere. Message.id is nullable; clientId is the reliable key.

Schema and introspection

TrapWhat to do
Introspection is not available.Use the bundled SDL and validate operations against it before running them.
The bundle is a trimmed copy.It carries the surfaces a scaffolded app works with. A name that is not in it is a name to treat as unavailable here.
The bundle carries names and types only.No descriptions and no deprecated members, so a name you cannot find is not necessarily a name you got wrong — it may be one this bundle deliberately does not carry. Prose about a surface lives in the module guides, not in the SDL.
Which error codes a mutation returns is not in the schema.There is no per-mutation list to read. Take the codes from DefinedErrorCode, from the module guide covering that surface, and from what the API returns — and switch on the raw extensions.code string with a fallback. The details are on errors.
Mutation casing is inconsistent.whatsAppTextMessageSend sits next to whatsappAttachmentMessageSend; tiktokTextMessageSend returns TikTokOutTextMessage. Copy names character for character from the schema instead of deriving them.

Withdrawn, and what to use instead

These names are not in the bundle. You will meet them in older code and older examples.

WithdrawnUse
root searchContacts / countContactsbot.contactsConnection / bot.contactsCount
bot.contactChatsCountbot.contactChatsCountV2(filter:)
bot.contactDealsTotalsByStagesbot.contactDealsByStages(filter:)

Pagination

TrapWhat to do
Bot.whatsAppTemplates accepts first, after and before and ignores all three.It returns the full list every time, and the field is nullable. The same absence, more honestly stated, applies to goodsCatalog, specialists, contactScopes, members, invites, bookingsV2, Workspace.bots and currentUser.workspaces, which have no pagination arguments at all.
Message history flips direction.No cursor or after gives newest-first descending; before gives ascending. first is required on the contact-ish connections and optional on messages. The rules are on the pagination page.

Data semantics

TrapWhat to do
"Deleted or inaccessible" is modelled three different ways.Union branches (DeletedGoodsService, DeletedSpecialist), boolean flags (PublicUserAccount.isUnknown, TikTokPost.isUnknown — when true, ignore every other field), and stub types (UnavailableContact, which means no permission and holds empty data; UnavailableTaskData). Handle all three; there is no single shape to check.
File.status == Expired means the file is gone.Do not request and do not render any other field of it.
Datetime attribute values are not RFC3339.An attribute whose AttributeDataType is datetime carries a millisecond-timestamp string, like "1720456863000". Only the Time scalar uses RFC3339.
Task.statuses is a history.The current status is the entry with the latest startedAt, and a task past its deadline has failed even with no Failed status. See files and tasks.
contactsCount and contactsTotalCount answer different questions.contactsCount respects the caller's assignee visibility restrictions and is what a contacts table should show; contactsTotalCount ignores them and is what a broadcast size estimate wants. Pick deliberately — both return an Int! and neither says which it is.
Creating a platform link silently replaces the active one.One link per bot, platform and kind, and the API keeps no used, expired or revoked state. Read bot.activePlatformConnectionLinks and bot.activePlatformAccessRefreshLinks before minting a new one; a link gone from those maps is gone.

The transport-level traps live with their subjects: an Unauthorized arriving inside an HTTP 200 and a relayed code nested one level down are on errors; a socket that replays nothing on reconnect and the per-token rate limit are on transport and auth; uploads never going through GraphQL is on files and tasks.

On this page