Pagination
Relay cursors with a scalar and a page-info type per connection, the direction flip on message history, where first is required, and the connections that accept pagination arguments and ignore them.
Connections are Relay-shaped, but almost nothing about them is shared — there are fourteen cursor scalars and fourteen page-info types, one set per connection.
type MessagePage {
edges: [MessageEdge!]!
pageInfo: MessagePageInfo!
}
type MessageEdge {
node: Message!
cursor: MessagesCursor!
}
type MessagePageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: MessagesCursor
endCursor: MessagesCursor
}The scalars are MessagesCursor, ContactSearchCursor, BotsCursor, BotAttributeCursor,
WhatsAppTemplateCursor, KeywordRuleCursor, CommentReplyRulesCursor, FuelyBroadcastCursor,
MetaAdCursor, InstagramMediasCursor, CoworkerConversationsCursor, CoworkerMessagesCursor,
FbPageCursor and FbPagePostCursor. All of them are opaque strings: echo the values you get back
from pageInfo and edges, and never build one.
Two page-info types are named to catch you. One is called exactly PageInfo, which reads like the
shared one and is not — it belongs to BotsConnectionV2, and its cursors are BotsCursor. The
other is FbPagePostInfo, the only one whose name does not end in PageInfo, and the only one
with no hasPreviousPage.
Where first is required
messages(first: Int, after: MessagesCursor, before: MessagesCursor): MessagePage!
contactsConnection(platforms: [Platform!]!, first: Int!, before: ContactSearchCursor, after: ContactSearchCursor, segment: SegmentInput, orderBy: ContactSearchOrderByInput): ContactConnection!
contactDealsConnection(first: Int!, before: ContactSearchCursor, after: ContactSearchCursor, assigneeFilter: ContactAssigneeFilter!, salesStageV2Filter: SalesStageV2!): ContactConnection!
contactChatsConnection(first: Int!, before: ContactSearchCursor, after: ContactSearchCursor, assigneeFilter: ContactAssigneeFilter!, unreadOnly: Boolean!, salesStageV2Filter: [SalesStageV2!]!, textInputFilter: String): ContactConnection!first is optional on Conversation.messages and required on the three contact-ish connections.
It varies per field, so read the signature in the bundled schema instead of assuming.
Message history runs backwards
| Arguments | Order you get | What paging forward means |
|---|---|---|
No cursor, or after | Descending — newest message first. | after: pageInfo.endCursor walks backwards into history, one older page at a time. |
before | Ascending. | Forward in time. |
A chat UI loads messages(first: 100), then answers "scroll up for older" with
messages(first: 100, after: <endCursor>). pageInfo.startCursor is the newest message's cursor,
which is what a mark-as-read call wants.
Connections that take pagination arguments and ignore them
| Field | What it actually does |
|---|---|
Bot.whatsAppTemplates(first, after, before) | Accepts all three and ignores them: it returns the full list every time. The field is also nullable. |
Bot.goodsCatalog, Bot.specialists, Bot.contactScopes, Bot.members, Bot.invites, Bot.bookingsV2, Workspace.bots, currentUser.workspaces | No pagination arguments at all: each returns its complete result every time. bookingsV2 takes a start and an end time, but nothing to page with. |
Accumulating pages alongside live updates
The chat, contact and deal lists are forward-only in practice: page with after: pageInfo.endCursor, guard on hasNextPage, and never start a page fetch while the previous one is
still in flight.
| Rule | Why |
|---|---|
| De-duplicate by node id, replacing rather than appending. | An incoming page or update can contain nodes you already hold. |
| Re-sort yourself. | contactsChatUpdates and contactsDealUpdates tell you what changed, not where it belongs — sort the chat list by lastConversationMessageTime descending, deals by lastSalesStageUpdateTime descending. |
Merge messages by clientId, never by id. | Message.id is nullable; clientId is the reliable key. |
| Treat an invalid-cursor error as "start again". | An after cursor that has fallen out of the result window stops resolving; refetch from the first page. |
Changing any filter argument resets the connection. The cursors you hold belong to the old argument set, so the accumulated edges have to be thrown away — keep them and the list quietly becomes two different result sets stacked on top of each other.
Errors
The Chatfuel API's error envelope — machine-readable codes in extensions, the DefinedErrorCode family, and the rules that catch people.
Files and tasks
How a file gets into the Chatfuel API — REST upload, then a FileID a mutation can reference — the File lifecycle, and how a long-running job reports its progress.