Skip to content

02 Get your data in /Integrations

Botpress

Send bot conversations from Botpress to Dimension Labs with the universal API.

  1. B BotpressTickets, chats, calls, reviews
  2. ⟳ Your jobPull new · map fields · every 15 min
  3. { } Universal APIPOST /track · /trackhistorical
  4. 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.

  • 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. 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.
  2. 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.

List messages created since your last run, across all of the bot’s conversations:

Terminal window
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"
  • afterDate and beforeDate are ISO 8601 times in UTC, both inclusive. Add conversationId to list one conversation’s messages.
  • Messages come back newest first. Pass meta.nextToken as nextToken until the response has none (pagination).
  • Each message has direction (incoming from the user, outgoing from the bot), type, payload, userId, conversationId and createdAt. The text is in payload.text for 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).

Dimension LabsBotpressNotes
textpayload.textSkip messages without text (images, cards).
userIduserId of the conversation’s incoming messagesThe 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.
incomingdirectiontrue for incoming, false for outgoing.
sessionIdconversationIdPrefix it, for example botpress-<conversationId>.
conversationIdconversationIdSame value as sessionId.
dimensionlabs_timestampcreatedAtConvert with toMillis.
platformJsontype, idtype 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.

// 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;
}

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-01 sends 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.

Checked against Botpress’s documentation on 2026-10-26.