Pagination
Relay cursors with a scalar and a PageInfo 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 fifteen cursor
scalars and fourteen PageInfo 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,
CommentReplyRuleV2Cursor, 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. One PageInfo type is called exactly that, which reads like the shared one and is not —
it belongs to BotsConnectionV2, and its cursors are BotsCursor.
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 four 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.