02 Get your data in /Integrations
Botpress
Send bot conversations from Botpress to Dimension Labs with the universal API.
- B BotpressTickets, chats, calls, reviews
- ⟳ Your jobPull new · map fields · every 15 min
- { } Universal APIPOST /track · /trackhistorical
- Dimension LabsRead in full · enriched daily
This brings in your Botpress bot conversations, message by message: what each user wrote and what the bot answered. Each Botpress conversation becomes one conversation. You run it on your side: a small script pulls from the Botpress API and posts each message to the universal API.
What you need
Section titled “What you need”- A Botpress account with access to the bot’s workspace, to create a personal access token.
- The bot id.
- A Dimension Labs API key for a new Universal API source. See Your API key.
1. Get API access in Botpress
Section titled “1. Get API access in Botpress”- Generate a personal access token (PAT) in the Profile Settings section of your Botpress account (authentication). Store it in a secrets manager: it has the same access you have.
- Open the bot’s dashboard. The bot id is the letters and numbers right after
chatbots/in the URL (raw conversations).
Send both on every request: Authorization: Bearer YOUR_PAT and x-bot-id: YOUR_BOT_ID.
2. Pull new messages
Section titled “2. Pull new messages”List messages created since your last run, across all of the bot’s conversations:
curl -H "Authorization: Bearer $BOTPRESS_TOKEN" -H "x-bot-id: $BOTPRESS_BOT_ID" \ "https://api.botpress.cloud/v1/chat/messages?afterDate=2026-10-01T00:00:00.000Z"afterDateandbeforeDateare ISO 8601 times in UTC, both inclusive. AddconversationIdto list one conversation’s messages.- Messages come back newest first. Pass
meta.nextTokenasnextTokenuntil the response has none (pagination). - Each message has
direction(incomingfrom the user,outgoingfrom the bot),type,payload,userId,conversationIdandcreatedAt. The text is inpayload.textfor text messages, choice cards and quick replies. Images have no text. - Botpress keeps conversations and messages for 90 days (retention period).
Botpress uses a sliding-window rate limiter. Going over returns 429, and every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds until the window resets) headers (rate limiting).
3. Map the fields
Section titled “3. Map the fields”| Dimension Labs | Botpress | Notes |
|---|---|---|
text | payload.text | Skip messages without text (images, cards). |
userId | userId of the conversation’s incoming messages | The end user’s Botpress id. The bot’s messages can carry a different userId, so use the end user’s on both sides. Fall back to the sessionId. |
incoming | direction | true for incoming, false for outgoing. |
sessionId | conversationId | Prefix it, for example botpress-<conversationId>. |
conversationId | conversationId | Same value as sessionId. |
dimensionlabs_timestamp | createdAt | Convert with toMillis. |
platformJson | type, id | type is the message type, such as text, choice or quick_reply. |
Personal data. Users sometimes type names, emails or order numbers: redact them before you send. See PII redaction. Don’t copy message tags into platformJson without checking them: channel integrations can put their own ids there.
4. Send it
Section titled “4. Send it”// botpress.mjs. Environment: BOTPRESS_TOKEN, BOTPRESS_BOT_ID, DL_API_KEY.import { sendToDimensionLabs, toMillis } from './dimensionlabs.mjs';
const HOUR = 60 * 60 * 1000;const sleep = (ms) => new Promise((r) => setTimeout(r, ms));const BASE = 'https://api.botpress.cloud/v1/chat';const headers = { Authorization: `Bearer ${process.env.BOTPRESS_TOKEN}`, 'x-bot-id': process.env.BOTPRESS_BOT_ID };
// Retries Botpress's rate limits (429) and server errors (5xx), honoring Retry-After or RateLimit-Reset.async function get(url) { for (let attempt = 0; ; attempt++) { const res = await fetch(url, { headers }); if (res.ok) return res.json(); if ((res.status !== 429 && res.status < 500) || attempt === 5) throw new Error(`Botpress returned ${res.status}: ${await res.text()}`); const wait = Number(res.headers.get('retry-after')) || Number(res.headers.get('ratelimit-reset')) || 2 ** attempt; await sleep(wait * 1000); }}
// Reads every page of messages matching the query.async function listMessages(query) { const messages = []; let nextToken; do { const q = new URLSearchParams(query); if (nextToken) q.set('nextToken', nextToken); const page = await get(`${BASE}/messages?${q}`); messages.push(...(page.messages ?? [])); nextToken = page.meta?.nextToken; } while (nextToken); return messages;}
// Sends every message created at or after `since` (Unix ms). Returns the new `since`.export async function syncBotpress({ since, historical = false }) { let newest = since; const byConversation = new Map(); for (const m of await listMessages({ afterDate: new Date(since).toISOString() })) { if (!byConversation.has(m.conversationId)) byConversation.set(m.conversationId, []); byConversation.get(m.conversationId).push(m); } for (const [conversationId, messages] of byConversation) { const id = `botpress-${conversationId}`.replace(/:/g, '-'); // The end user is whoever sent the incoming messages. Look further back if this batch has none. let user = messages.find((m) => m.direction === 'incoming')?.userId; if (!user) user = (await listMessages({ conversationId })).find((m) => m.direction === 'incoming')?.userId; messages.sort((a, b) => toMillis(a.createdAt) - toMillis(b.createdAt)); for (const m of messages) { const ts = toMillis(m.createdAt); newest = Math.max(newest, ts); const text = m.payload?.text; if (typeof text !== 'string' || !text.trim()) continue; await sendToDimensionLabs({ text, userId: user ? String(user).replace(/:/g, '-') : id, incoming: m.direction === 'incoming', sessionId: id, conversationId: id, dimensionlabs_timestamp: ts, platformJson: { type: m.type, botpress_message_id: m.id }, }, { historical: historical || ts < Date.now() - HOUR }); } } return newest;}5. Run it
Section titled “5. Run it”Save the code as botpress.mjs in a project set up as in Set up your project, and run it with the scheduled sync runner: import { syncBotpress as sync } from './botpress.mjs';.
- History first:
node run.mjs 2026-08-01sends every message since that date to/trackhistorical. Botpress keeps messages for 90 days, so that is as far back as the API goes. Then start a backfill from the Backfill tab. - Then every 15 minutes: the scheduled job sends new messages to
/track, and they are enriched automatically every day. A message that is more than an hour old when the job sees it goes to/trackhistorical, and needs a backfill. - Keep it running: messages older than 90 days are deleted, so a longer gap can’t be filled from the API.
- Re-runs are safe: duplicates are removed automatically.
A history load reads every message in one pass, then sends them conversation by conversation; the code waits whenever Botpress asks it to. You can also write the converted messages to a JSONL file instead of posting them, upload it (check it with the file checker), then run a backfill.
Botpress reference
Section titled “Botpress reference”- Authentication
- Raw conversations with the Botpress API
- listMessages
- Pagination
- Rate limiting
- Retention period
Checked against Botpress’s documentation on 2026-10-26.