# Gladly

> Send Gladly conversation items to Dimension Labs with the universal API.

This brings in the conversation items on your Gladly customer timelines: chat, email, SMS and other messages from customers, agents and Gladly's AI, grouped by conversation. You run it on your side: a small script picks up Gladly's export files and posts each message to the [universal API](/api/tracker).

Gladly works differently from the other support desks: its data arrives in export files, once a day by default, so most messages are more than an hour old when the file appears. Those go to `/trackhistorical`, and their Dimensions need a [backfill](/dimensions/backfill) after each export.

## What you need

- A Gladly administrator, to give a user the **API User** permission. Gladly recommends a dedicated user for API calls.
- An API token for that user, and the user's email address.
- 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 Gladly

1. Open the menu in the top left corner and go to **Settings > Users**.
2. Open the user's profile, select the **API User** permission while keeping their Agent role, and click **Save**.
3. Signed in as that user, open **More settings**, click **API Tokens**, then **Create Token**. You only see the token once.
4. Authenticate with Basic auth: the user's email as the username and the API token as the password ([getting started](https://developer.gladly.com/rest/#section/Getting-Started), [authentication](https://developer.gladly.com/rest/#section/Authentication)).

## 2. Pull new conversation items

The [Export API](https://developer.gladly.com/rest/#tag/Export) writes your conversations to files on a schedule. By default each organization has a daily export job. Gladly Support can change it to hourly, and hourly jobs are created roughly two hours after their period starts. Jobs and files are kept for 14 days.

1. [List jobs](https://developer.gladly.com/rest/#operation/findJobs) that completed since your cursor. `startAt` filters on the job's `updatedAt` and can't be more than 14 days ago:

```bash
curl -u "$GLADLY_EMAIL:$GLADLY_API_TOKEN" \
  "https://YOUR_ORG.gladly.com/api/v1/export/jobs?status=COMPLETED&startAt=2026-10-13T00:00:00.000Z"
```

2. For each job, [get the file](https://developer.gladly.com/rest/#operation/getFile) `conversation_items.jsonl`: `GET /api/v1/export/jobs/{jobId}/files/conversation_items.jsonl`. Each line is one conversation item.

For one conversation at a time, [list items in a conversation](https://developer.gladly.com/rest/#operation/getConversationItems), `GET /api/v1/conversations/{conversationId}/items`, returns its timeline. The `Gladly-Limited-Data` response header says when the conversation has more items than were returned.

Rate limits, per Gladly: 10 requests per second for each HTTP method, unless an API documents its own limit. Responses carry `Ratelimit-Limit-Second` and `Ratelimit-Remaining-Second` headers, and going over returns `429` ([default rate limit](https://developer.gladly.com/rest/#section/Default-Rate-Limit), [handling rate limit](https://developer.gladly.com/rest/#section/Handling-Rate-Limit)).

## 3. Map the fields

| Dimension Labs | Gladly | Notes |
|---|---|---|
| `text` | item `content.content` | Present on message items such as `CHAT_MESSAGE` and `EMAIL`. Skip items without it, such as `CONVERSATION_STATUS_CHANGE`, `TOPIC_CHANGE` and `CONVERSATION_NOTE` (an internal note, with its text in `body`). |
| `userId` | item `customerId` | Gladly's internal customer id, on every item in the conversation. |
| `incoming` | item `initiator.type` | `true` when it is `CUSTOMER`. `AGENT` is a person on your team and `SYSTEM` is automation, such as Gladly's AI. |
| `sessionId` | item `conversationId` | Gladly ids have no colons. |
| `conversationId` | item `conversationId` | Same value as `sessionId`. |
| `dimensionlabs_timestamp` | item `timestamp` | Convert with `toMillis`. |
| `platformJson` | item `content.type`, `content.sessionId`, `initiator.type`, `id` | `content.type` is the channel. To join to your own data, `customers.jsonl` in the same job maps each customer to your `externalCustomerId`. |

**Personal data.** `customers.jsonl` also holds customer emails and names: don't send them. Message text can hold them too: redact it before you send. See [PII redaction](/data/pii-redaction).

## 4. Send it

```js
// gladly.mjs. Environment: GLADLY_ORG, GLADLY_EMAIL, GLADLY_API_TOKEN, DL_API_KEY.


const HOUR = 60 * 60 * 1000;
const DAY = 24 * HOUR;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const base = `https://${process.env.GLADLY_ORG}.gladly.com/api/v1/export/jobs`;
const auth = 'Basic ' + Buffer.from(`${process.env.GLADLY_EMAIL}:${process.env.GLADLY_API_TOKEN}`).toString('base64');

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

// Sends the items of every export job completed after `since` (Unix ms). Returns the new `since`.
export async function syncGladly({ since, historical = false }) {
  const startAt = Math.max(since + 1, Date.now() - 14 * DAY + HOUR); // Gladly keeps jobs for 14 days
  const jobs = await (await get(`${base}?status=COMPLETED&startAt=${new Date(startAt).toISOString()}`)).json();
  jobs.sort((a, b) => toMillis(a.updatedAt) - toMillis(b.updatedAt));
  let newest = since;
  for (const job of jobs) {
    if (job.files?.includes('conversation_items.jsonl')) {
      const lines = (await (await get(`${base}/${job.id}/files/conversation_items.jsonl`)).text()).split('\n');
      for (const line of lines) {
        if (!line.trim()) continue;
        const item = JSON.parse(line);
        const text = item.content?.content;
        if (typeof text !== 'string' || !text.trim()) continue;
        const ts = toMillis(item.timestamp);
        await sendToDimensionLabs({
          text,
          userId: item.customerId,
          incoming: item.initiator?.type === 'CUSTOMER',
          sessionId: item.conversationId,
          conversationId: item.conversationId,
          dimensionlabs_timestamp: ts,
          platformJson: { channel: item.content.type, chat_session_id: item.content.sessionId, initiator_type: item.initiator?.type, item_id: item.id },
        }, { historical: historical || ts < Date.now() - HOUR }); // most export items are older than an hour
      }
    }
    newest = Math.max(newest, toMillis(job.updatedAt));
  }
  return newest;
}
```

## 5. Run it

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

- **Which endpoint:** each item goes to `/track` if it is less than an hour old when the file is read, and to `/trackhistorical` otherwise. With Gladly's daily export, that is almost everything; even hourly jobs appear about two hours after their period starts. So plan on `/trackhistorical`.
- **Backfill after each export:** start a [backfill](/dimensions/backfill) from the **Backfill** tab once the day's job has been sent. A backfill typically shows up within the hour, depending on how much data is being enriched. Running the job once a day, after the export, keeps this to one backfill a day.
- **History:** `node run.mjs 2026-10-01` sends the jobs of the last 14 days (Gladly keeps no more). For older history, ask Gladly Support for a one-time export (Gladly notes a fee may apply beyond six months), [upload the file](/data/upload-a-file) (check it with the [file checker](/data/check-a-file)), then run a backfill.
- **Re-runs are safe:** duplicates are removed automatically.

## Gladly reference

- [Getting started](https://developer.gladly.com/rest/#section/Getting-Started)
- [Authentication](https://developer.gladly.com/rest/#section/Authentication)
- [Default rate limit](https://developer.gladly.com/rest/#section/Default-Rate-Limit)
- [Conversation items](https://developer.gladly.com/rest/#section/Conversation-Items)
- [Export](https://developer.gladly.com/rest/#tag/Export)
- [List jobs](https://developer.gladly.com/rest/#operation/findJobs)
- [Get file](https://developer.gladly.com/rest/#operation/getFile)
- [List items in a conversation](https://developer.gladly.com/rest/#operation/getConversationItems)
- [Gladly developer documentation index](https://developer.gladly.com/llms.txt)

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