# Ada

> Send AI agent conversations from Ada to Dimension Labs with the universal API.

This brings in your Ada AI agent conversations, message by message: what each customer wrote and what the AI agent answered. You run it on your side with the [universal API](/api/tracker), using Ada's Data Export API.

## What you need

- An Ada dashboard account that can open the API keys page.
- An Ada API key and your bot handle (the `<handle>` in `https://<handle>.ada.support`).
- 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 Ada

Ada's own steps are in [Authentication](https://docs.ada.cx/reference/introduction/authentication).

1. In the Ada dashboard, open your API settings:
   - For a generative AI agent: **Config > PLATFORM > API keys**.
   - For a scripted bot: **Settings > Integrations > APIs**.
2. Click **Get started** (no keys yet) or **New API key**.
3. Enter a **Name** for the key and click **Generate key**.
4. Click **Copy** and store the key in a secrets manager. Ada shows it only once.

Send the key as a bearer token on every request: `Authorization: Bearer YOUR_ADA_API_KEY`.

## 2. Pull new messages

Use the Data Export API messages endpoint, filtered by creation time:

```bash
curl -H "Authorization: Bearer $ADA_API_KEY" \
  "https://YOUR_HANDLE.ada.support/api/v2/export/messages?created_since=2026-10-13T00:00:00Z&created_to=2026-10-13T23:59:59Z"
```

- `created_since` is required and must be an ISO 8601 UTC time ending in `Z`. If you leave out `created_to`, Ada returns only the 7 days after `created_since`.
- Each response has `data` (the messages) and `meta.next_page_uri`. Keep requesting `next_page_uri` until it is empty.
- `page_size` defaults to, and is capped at, 10,000 records.
- For conversation-level fields (CSAT, language, escalation, topic), call `GET /api/v2/export/conversations` with `updated_since`.

Ada's documented limits for the Data Export API (v2): 10 requests per second per endpoint, 100 per minute and 15,000 per day; a query's end date can be at most 60 days after its start date; and the API holds the past 12 months of data. A `429` response carries a `Retry-After` header in seconds.

## 3. Map the fields

| Dimension Labs | Ada | Notes |
|---|---|---|
| `text` | `message_data.body` | Present on text messages. Skip messages without a `body` (greetings, quick replies, meta events). |
| `userId` | `chatter_id` | Ada's id for the chatter. Use it on both the chatter's and the AI agent's messages. |
| `incoming` | `sender` | Ada's message object docs: `sender` is a unique id for a chatter, or `Ada` for the AI agent or scripted bot. `incoming` is `true` when `sender` is not `Ada` (compare case-insensitively). |
| `sessionId` | `conversation_id` | |
| `conversationId` | `conversation_id` | |
| `dimensionlabs_timestamp` | `date_created` | Convert with `toMillis`. |
| `platformJson` | `answer_title`, `review`, `link_was_clicked`, `_id` | Add conversation fields from the conversations endpoint if you need them: `csat`, `language`, `is_escalated`, `platform`, `end_user_id`. |

**Personal data.** Chatters sometimes type names, emails or order numbers: redact them before you send. See [PII redaction](/data/pii-redaction).

## 4. Send it

```js
// ada.mjs. Environment: ADA_HANDLE, ADA_API_KEY, DL_API_KEY.


const HOUR = 60 * 60 * 1000;
const WINDOW = 60 * 24 * HOUR; // Ada allows at most 60 days per query
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const BASE = `https://${process.env.ADA_HANDLE}.ada.support`;
const iso = (ms) => new Date(ms).toISOString().replace(/\.\d+Z$/, 'Z');

// Retries Ada'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: `Bearer ${process.env.ADA_API_KEY}` } });
    if (res.ok) return res.json();
    if ((res.status !== 429 && res.status < 500) || attempt === 5) throw new Error(`Ada returned ${res.status}: ${await res.text()}`);
    await sleep((Number(res.headers.get('retry-after')) || 2 ** attempt) * 1000);
  }
}

// Sends every message created at or after `since` (Unix ms). Returns the new `since`.
export async function syncAda({ since, historical = false }) {
  let newest = since;
  for (let from = since; from < Date.now(); from += WINDOW) {
    const to = Math.min(from + WINDOW, Date.now());
    let next = `${BASE}/api/v2/export/messages?created_since=${iso(from)}&created_to=${iso(to)}`;
    while (next) {
      const page = await get(new URL(next, BASE).href); // works whether next_page_uri is a full URL or a path
      for (const m of page.data ?? []) {
        const text = m.message_data?.body;
        if (!text || !text.trim()) continue;
        const id = m.conversation_id.replace(/:/g, '-');
        const ts = toMillis(m.date_created);
        await sendToDimensionLabs({
          text,
          userId: m.chatter_id || id,
          incoming: String(m.sender).toLowerCase() !== 'ada',
          sessionId: id,
          conversationId: id,
          dimensionlabs_timestamp: ts,
          platformJson: { answer_title: m.answer_title, review: m.review, ada_message_id: m._id },
        }, { historical: historical || ts < Date.now() - HOUR });
        newest = Math.max(newest, ts);
      }
      next = page.meta?.next_page_uri;
    }
  }
  return newest;
}
```

## 5. Run it

Save the code as `ada.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 { syncAda as sync } from './ada.mjs';`.

- **History first:** `node run.mjs 2025-11-01` sends every message since that date to `/trackhistorical`, in 60-day windows. Ada's API holds the past 12 months. 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.

You can also [upload a file](/data/upload-a-file) of exported messages and check it first with the [file checker](/data/check-a-file), then run a backfill.

## Ada reference

- [Authentication](https://docs.ada.cx/reference/introduction/authentication)
- [Limits](https://docs.ada.cx/reference/introduction/limits)
- [Data Export API overview](https://docs.ada.cx/reference/data-export/overview)
- [Data Export API: getting started](https://docs.ada.cx/reference/data-export/getting-started)
- [Return messages matching the parameters](https://docs.ada.cx/data-export-message-object/get-messages)
- [Message object](https://docs.ada.cx/data-export-message-object)
- [Return conversations matching the parameters](https://docs.ada.cx/data-export-conversation-object/get-conversations)

Checked against Ada's documentation on 2026-10-13.
