# Re:amaze

> Send Re:amaze conversations and their messages to Dimension Labs with the universal API.

This brings in each Re:amaze conversation as one conversation: the customer's messages and your staff's replies, across email, chat, social, SMS, WhatsApp and the other channels a brand connects. You run it on your side: a small script pulls from the Re:amaze API and posts each message to the [universal API](/api/tracker).

## What you need

- A Re:amaze user whose API token you can use. Every user has their own token, and the API acts as that user.
- The login email of that user, and the host of each brand you want to bring in, for example `yourbrand.reamaze.io`. It's on the Brand Settings page.
- A Dimension Labs API key for a new **Universal API** source. See [Your API key](/api/tracker#your-api-key).

## 1. Get API access in Re:amaze

1. In your Re:amaze account, go to **Settings**.
2. Under **Developer**, click **API Token**.
3. Click **Generate New Token** and copy it.
4. Authenticate with Basic auth: the user's login email and the token. Send `Accept: application/json` on every request, or add `.json` to the URL ([API introduction](https://www.reamaze.com/api)).

Requests are scoped by brand, through the host. An account with several brands needs one pull per brand host.

## 2. Pull new messages

[Retrieve messages](https://www.reamaze.com/api/get_messages) across every conversation in the brand, filtered by when they were written. Ask for customer and staff messages separately, so you know who wrote each one:

```bash
curl -u "$REAMAZE_EMAIL:$REAMAZE_API_TOKEN" -H "Accept: application/json" \
  "https://YOUR_BRAND.reamaze.io/api/v1/messages?filter=customer&start_date=2026-10-01T00:00:00Z&page=1"
```

`start_date` and `end_date` (ISO 8601) filter on the message's creation date. `filter=customer` returns only customer messages and `filter=staff` only staff messages. Results come newest first, 30 to a page: keep adding 1 to `page` up to the `page_count` in the response.

Each message has `body`, `visibility`, `origin`, `created_at` and its `conversation` (`slug`, `subject`, `category`). The conversation `slug` uniquely identifies a conversation.

Rate limits, per Re:amaze: a set number of requests per minute per API token, which Re:amaze doesn't publish. Going over returns `429` ([API introduction](https://www.reamaze.com/api)).

## 3. Map the fields

| Dimension Labs | Re:amaze | Notes |
|---|---|---|
| `text` | message `body` | Skip messages where `visibility` is `1` (internal note) or `2` (collision detected message). Keep `0` (regular). |
| `userId` | conversation `slug` | Messages identify the sender only by name and email, so use the `sessionId`. |
| `incoming` | the `filter` you asked for | `true` for messages from `filter=customer`, `false` for messages from `filter=staff`. |
| `sessionId` | conversation `slug` | Prefix it with the brand, for example `reamaze-yourbrand-knock-knock`. |
| `conversationId` | conversation `slug` | Same value as `sessionId`. |
| `dimensionlabs_timestamp` | message `created_at` | Convert with `toMillis`. Re:amaze times carry a time zone offset. Messages with the same time in one conversation get 1,000 ms added each. |
| `platformJson` | message `origin`; conversation `category.slug`, `category.channel` | `origin` says where the message came from, for example `1` for email and `9` for SMS. |

**Personal data.** Don't send the message's `user` or `recipients`, or the conversation's `author`: they hold names and email addresses. Messages can hold addresses, phone numbers and order details: redact them before you send. See [PII redaction](/data/pii-redaction).

## 4. Send it

```js
// reamaze.mjs. Environment: REAMAZE_BRAND (yourbrand, from yourbrand.reamaze.io), REAMAZE_EMAIL, REAMAZE_API_TOKEN, DL_API_KEY.


const HOUR = 60 * 60 * 1000;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const brand = process.env.REAMAZE_BRAND;
const base = `https://${brand}.reamaze.io/api/v1`;
const auth = 'Basic ' + Buffer.from(`${process.env.REAMAZE_EMAIL}:${process.env.REAMAZE_API_TOKEN}`).toString('base64');

// Retries Re:amaze's rate limits (429) and server errors (5xx), honoring Retry-After.
async function get(url) {
  for (let attempt = 0; ; attempt++) {
    const res = await fetch(url, { headers: { Authorization: auth, Accept: 'application/json' } });
    if (res.ok) return res.json();
    if ((res.status !== 429 && res.status < 500) || attempt === 5) throw new Error(`Re:amaze returned ${res.status}: ${await res.text()}`);
    await sleep((Number(res.headers.get('retry-after')) || 2 ** (attempt + 2)) * 1000);
  }
}

// Every message of one kind (customer or staff) written at or after `since`.
async function messages(filter, since) {
  const all = [];
  const start = encodeURIComponent(new Date(since).toISOString());
  for (let page = 1, pages = 1; page <= pages; page++) {
    const res = await get(`${base}/messages?filter=${filter}&start_date=${start}&page=${page}`);
    all.push(...res.messages);
    pages = res.page_count;
  }
  return all.map((m) => ({ ...m, incoming: filter === 'customer' }));
}

// Sends every regular message written at or after `since` (Unix ms). Returns the new `since`.
export async function syncReamaze({ since, historical = false }) {
  let newest = since;
  const all = [...await messages('customer', since), ...await messages('staff', since)]
    .filter((m) => m.visibility === 0 && m.conversation?.slug && m.body?.trim() && toMillis(m.created_at) >= since)
    .sort((a, b) => a.conversation.slug.localeCompare(b.conversation.slug) || toMillis(a.created_at) - toMillis(b.created_at));
  let last = { slug: null, ts: 0 };
  for (const m of all) {
    const slug = m.conversation.slug;
    let ts = toMillis(m.created_at);
    if (slug === last.slug && ts <= last.ts) ts = last.ts + 1000;
    last = { slug, ts };
    const id = `reamaze-${brand}-${slug}`;
    await sendToDimensionLabs({
      text: m.body,
      userId: id,
      incoming: m.incoming,
      sessionId: id,
      conversationId: id,
      dimensionlabs_timestamp: ts,
      platformJson: { brand, origin: m.origin, category: m.conversation.category?.slug, channel: m.conversation.category?.channel },
    }, { historical: historical || ts < Date.now() - HOUR });
    newest = Math.max(newest, toMillis(m.created_at));
  }
  return newest;
}
```

## 5. Run it

Save the code as `reamaze.mjs` in a project set up as in [Set up your project](/api/tracker#set-up-your-project), and run it with the [scheduled sync](/api/scheduled-sync) runner: `import { syncReamaze as sync } from './reamaze.mjs';`. For several brands, run one job per brand, each with its own `REAMAZE_BRAND` and its own cursor file.

- **History first:** `node run.mjs 2025-01-01` sends every message since that date to `/trackhistorical`. Then start a [backfill](/dimensions/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.
- **Re-runs are safe:** duplicates are removed automatically.

Pages hold 30 messages, so a history load makes many calls; the code waits whenever Re:amaze returns `429`. You can also [upload a file](/data/upload-a-file) of exported conversations with one row per message (check it with the [file checker](/data/check-a-file)), then run a backfill.

## Re:amaze reference

- [API introduction: tokens, brand scope, authentication and rate limiting](https://www.reamaze.com/api)
- [Retrieving messages](https://www.reamaze.com/api/get_messages)
- [Retrieving conversations](https://www.reamaze.com/api/get_conversations)
- [Get conversation](https://www.reamaze.com/api/get_conversation)

Checked against Re:amaze's documentation on 2026-10-26.
