# Cognigy

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

This brings in your Cognigy.AI conversations, message by message: what each end user wrote, what the AI agent answered and what a human agent wrote after a handover. Each Cognigy session becomes one conversation. You run it on your side: a small script pulls from the Cognigy OData Analytics endpoint and posts each message to the [universal API](/api/tracker).

## What you need

- A Cognigy user with the `odata` global role, and an API key for that user.
- Your OData domain: `https://odata-trial.cognigy.ai` on a trial, `https://odata-<your-domain>.cognigy.ai` on shared SaaS, `https://odata-<backend-url>` on premises, or `https://cognigy-odata-<region>.nicecxone.com` through NICE CXone. On dedicated SaaS, ask Cognigy for the URL.
- Optionally, your project id, to give the job its own rate limit budget.
- 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 Cognigy

1. Make sure the user has the `odata` global role ([OData authentication](https://docs.cognigy.com/ai/agents/analyze/odata)). An API key has the same permissions as its user.
2. Open **My Profile**: click your user icon in the upper-right corner of the Main page.
3. In the **API Keys** section, click **+**, enter a name and click **Confirm**.
4. Click the key's value to copy it, and store it in a secrets manager. Cognigy shows it only once ([My Profile](https://docs.cognigy.com/ai/administer/user-menu/my-profile)).

Send the key in the `apikey` header. Cognigy also accepts it as a query parameter, but the header keeps it out of URLs and logs.

## 2. Pull new messages

Use the `Conversations` collection of OData endpoint version 2.4 (Cognigy 4.65 and later). It logs every session message from the end user, the AI agent and the human agent, one record per message:

```bash
curl -H "apikey: $COGNIGY_API_KEY" \
  "https://odata-trial.cognigy.ai/v2.4/Conversations?\$filter=timestamp%20ge%20'2026-10-01T00:00:00.000Z'&\$orderby=timestamp%20asc&\$top=1000"
```

- Dates are ISO 8601 in UTC, in single quotes. `$filter`, `$orderby`, `$top`, `$skip` and `$select` are supported, and each may appear only once in a URL.
- High `$skip` values can time out. Page by time instead (keyset pagination): ask again from the last `timestamp` you received.
- Add `projectId eq '<project-id>'` to the filter to use that project's own request budget.
- OData data isn't updated in real time, so the code re-reads the last hour on every run.

Cognigy's default request limits per budget: a burst of 50 requests, then 10 requests a second, with at most 4 requests at a time. Going over returns `429` with a `Retry-After` header in seconds ([request limits](https://docs.cognigy.com/ai/agents/analyze/odata)).

Cognigy deletes analytics data, Conversations included, after a time to live set for your organization ([data expiration](https://docs.cognigy.com/ai/agents/analyze/data-expiration)). History only goes back that far.

## 3. Map the fields

| Dimension Labs | Cognigy | Notes |
|---|---|---|
| `text` | `inputText` | Skip records where it is empty. Skip `source` `suggestion`: those are suggestions nobody sent. |
| `userId` | `contactId` | The id of the connecting user. Use it on both sides of the conversation. Fall back to the `sessionId`. |
| `incoming` | `source` | `true` when `source` is `user`. `bot` (the AI agent) and `agent` (a human agent) are `false`. |
| `sessionId` | `sessionId` | Prefix it, for example `cognigy-<sessionId>`. |
| `conversationId` | `sessionId` | Same value as `sessionId`. Keeps the conversation together across a handover to a human agent. |
| `dimensionlabs_timestamp` | `timestamp` | Convert with `toMillis`. |
| `platformJson` | `channel`, `flowName`, `projectName`, `endpointName`, `localeReferenceId`, `inHandoverConversation`, `rating`, `id` | `rating` is `1` or `-1` when the user rated the session. |

If an endpoint has **Mask Sensitive Analytics** turned on, `inputText` arrives masked; if **Collect Analytics** is off, nothing is logged at all. Version 2.4 names the record id `id`; version 2.3 used `_id`.

**Personal data.** Check what your channels put in `contactId`: if it holds a phone number or email address, use the `sessionId` instead. End users sometimes type names, emails or order numbers: redact them before you send, or turn on Cognigy's own Data Redaction for the project. See [PII redaction](/data/pii-redaction).

## 4. Send it

```js
// cognigy.mjs. Environment: COGNIGY_ODATA_URL (for example https://odata-trial.cognigy.ai), COGNIGY_API_KEY, COGNIGY_PROJECT_ID (optional), DL_API_KEY.


const HOUR = 60 * 60 * 1000;
const PAGE = 1000;
const OVERLAP = HOUR; // OData isn't real time: re-read the last hour. Duplicates are removed.
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const BASE = `${process.env.COGNIGY_ODATA_URL.replace(/\/$/, '')}/v2.4/Conversations`;
const PROJECT = process.env.COGNIGY_PROJECT_ID;
const iso = (ms) => new Date(ms).toISOString();
const safe = (s) => String(s).replace(/:/g, '-');

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

// Sends every message logged at or after `since` (Unix ms). Returns the new `since`.
export async function syncCognigy({ since, historical = false }) {
  let newest = since;
  let from = Math.max(0, since - OVERLAP);
  let skip = 0; // records already read that share the `from` timestamp
  for (;;) {
    const filter = [`timestamp ge '${iso(from)}'`, PROJECT && `projectId eq '${PROJECT}'`].filter(Boolean).join(' and ');
    const rows = (await get(`${BASE}?$filter=${encodeURIComponent(filter)}&$orderby=${encodeURIComponent('timestamp asc,id asc')}&$top=${PAGE}${skip ? `&$skip=${skip}` : ''}`)).value ?? [];
    for (const r of rows) {
      const ts = toMillis(r.timestamp);
      newest = Math.max(newest, ts);
      if (r.source === 'suggestion' || !r.inputText?.trim()) continue;
      const id = `cognigy-${safe(r.sessionId)}`;
      await sendToDimensionLabs({
        text: r.inputText,
        userId: r.contactId ? safe(r.contactId) : id,
        incoming: r.source === 'user',
        sessionId: id,
        conversationId: id,
        dimensionlabs_timestamp: ts,
        platformJson: {
          channel: r.channel, flow: r.flowName, project: r.projectName, endpoint: r.endpointName, locale_id: r.localeReferenceId,
          source: r.source, in_handover: r.inHandoverConversation, rating: r.rating, cognigy_record_id: r.id,
        },
      }, { historical: historical || ts < Date.now() - HOUR });
    }
    if (rows.length < PAGE) break;
    // Keyset pagination: continue from the last timestamp, skipping the records already read at it.
    const lastTs = toMillis(rows[rows.length - 1].timestamp);
    const atLast = rows.filter((r) => toMillis(r.timestamp) === lastTs).length;
    skip = lastTs === from ? skip + atLast : atLast;
    from = lastTs;
  }
  return newest;
}
```

## 5. Run it

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

- **History first:** `node run.mjs 2026-01-01` sends every message since that date to `/trackhistorical`, as far back as your data expiration allows. 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, so the one-hour overlap on each run costs nothing but a few extra calls.

The code sends one request at a time, well inside Cognigy's limits, and waits whenever Cognigy asks it to. The same OData feed also opens in Excel or Power BI, so you can export it to a file instead, [upload the file](/data/upload-a-file) (check it with the [file checker](/data/check-a-file)), then run a backfill.

## Cognigy reference

- [Cognigy OData Analytics endpoint](https://docs.cognigy.com/ai/agents/analyze/odata)
- [My Profile (API keys)](https://docs.cognigy.com/ai/administer/user-menu/my-profile)
- [Data expiration in Cognigy](https://docs.cognigy.com/ai/agents/analyze/data-expiration)

Checked against Cognigy's documentation on 2026-10-26.
