Skip to main content

Status and recent activity

GET /api/connections?userRef= answers the three questions you have about a user: are they connected, how active have they been, and is their history whole.
  • connected is true when at least one connection is active.
  • needsReauth is true when at least one connection is needs_reauth. This is the only status that should trigger a “please reconnect” message. A revoked connection was either replaced after a reconnect or deleted by the user on purpose.
  • recentConversationCount counts conversations whose first message is dated within the last 30 days, evaluated at request time. It measures what the user did, not what Verso imported: an old history imported yesterday counts for nothing, and continuing an old conversation does not move it into the window.
  • historyComplete is true once the whole history of the connection has been imported, and oldestConversationAt / newestConversationAt give its span (first message of the oldest and of the newest conversation). While the initial import is still running, historyComplete is false and the span only reflects what has arrived so far; the moment it turns true is announced by the history.completed webhook.
Always query by userRef. Connection ids change when a user reconnects, and one user may hold several connections. Each connection carries an accountId, stable for one ChatGPT account within your app: two connections with the same accountId are the same account, for example an old revoked one and its replacement. A ChatGPT account can be connected by only one user of your app. Each connection also carries what the provider says about the account: plan (free, go, plus, pro, team, enterprise…) and country (ISO 3166-1 alpha-2). Both are null until the first sync. Ads are shown to free and go accounts only. An expired session is detected at the next sync, so connected can stay true for a few hours after the provider invalidated the session. lastSyncAt tells you when it was last verified.

Your whole user base

For a periodic run over every user, GET /api/users returns pages of users in the same shape, oldest first.
limit goes up to 500, so ten thousand users take twenty calls, well within the rate limit of one minute. Users who opened a connect link but never finished logging in are listed with connected: false and no connections.

Reading conversations

GET /api/conversations?connectionId= returns pages of conversations, newest update_time first.
Because the list is ordered by update_time, a conversation the user continues moves back to the top. To detect changes cheaply, compare update_time with what you stored.

Shape of a conversation

messages is the conversation’s current branch in chronological order. System messages and empty nodes are dropped; messages the user regenerated or branched away from are not included. Attachments are metadata only for now. The files are stored by Verso but cannot be retrieved through the API yet.

Ads shown in a conversation

ChatGPT shows sponsored results to Free and Go accounts under some answers. Each conversation carries them in ads, keyed to the user message they answered:
messageId is the id of the user message in payload.messages; visibility is allowed for an ad that was displayed. ads is read within two days of the conversation’s last update, the time the provider keeps it: it is empty for paid accounts (see plan on the connection), for conversations without any, and for conversations that were already older when the user connected. For those, and for every ad regardless of age, use the impressions below.

Every ad the account saw

The provider also keeps, for months, the history of every ad the account saw (viewed) or clicked (opened). Verso reads it back to its start over the first syncs, then picks up the new impressions at each sync, and matches each one to the conversation that was open at that moment. GET /api/ad-impressions?connectionId= lists them newest first:
conversationId is exact when Verso read the ad in the conversation (messageId set, the same id as in ads), the conversation active at that moment otherwise, and null when none of the imported conversations covers it. Each conversation also carries its own impressions as adImpressions, oldest first, so a conversation from before the user connected still shows the ads it had. A click is its own row, next to the viewed row of the same adId; adId is the ad’s request id, which ads[].adId extends with :<n>.

Sync cadence

Every connection is synced by a background worker that lists the account’s conversations, fetches the ones that changed, and stores them. The pace adapts to the account and to the provider’s limits. The initial backfill fetches the most recent conversations first, then works backwards; a large history takes hours because the provider limits how fast one account can be read. Each sync that inserts or updates at least one conversation sends data.imported. When the backfill reaches the end of the history, history.completed is sent once, with the connection’s figures, and historyComplete turns true on GET /api/connections.

Re-authentication

When the provider rejects the stored session, the connection becomes needs_reauth and reauth.needed is sent. Sign a new connect link:
After the user logs in, the new connection replaces the old one and sync resumes where it stopped. The conversations already imported stay readable in the meantime.