# Help Scout

> Send Help Scout conversations and their threads to Dimension Labs with the universal API.

This brings in each Help Scout conversation, its customer messages and your team's replies across email, chat and phone threads. You run it on your side: a small script pulls from the Help Scout Inbox API and posts each thread to the [universal API](/api/tracker).

## What you need

- An active, invited Help Scout user to own the app. Help Scout returns `401` for credentials not tied to one.
- An OAuth2 app's application ID and application secret, used with the client credentials flow.
- 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 Help Scout

1. Go to **Your Profile > My apps** and click **Create My App**.
2. Help Scout asks for a redirection URL. For the client credentials flow any valid URL works, such as `https://example.com`.
3. Copy the application ID and application secret.
4. Request an access token ([authentication](https://developer.helpscout.com/mailbox-api/overview/authentication/)):

```bash
curl -X POST https://api.helpscout.net/v2/oauth2/token \
  --data "grant_type=client_credentials" \
  --data "client_id=$HELPSCOUT_APP_ID" --data "client_secret=$HELPSCOUT_APP_SECRET"
```

The token is valid for 2 days. Request a new one when the API answers `401`. Send it as `Authorization: Bearer ACCESS_TOKEN`.

## 2. Pull new conversations

Use [List Conversations (v3)](https://developer.helpscout.com/mailbox-api/endpoints/conversations/list-v3/), which Help Scout recommends over v2 for performance, with `modifiedSince`. Add `status=all`, because only active conversations are listed by default.

```bash
curl -H "Authorization: Bearer $HELPSCOUT_TOKEN" \
  "https://api.helpscout.net/v3/conversations?status=all&modifiedSince=2026-10-13T00:00:00Z"
```

v3 uses cursor pagination: request `_links.next.href` until there is no `next` link. Results are always newest first.

For each conversation, [List Threads (v3)](https://developer.helpscout.com/mailbox-api/endpoints/conversations/threads/list-v3/): `GET /v3/conversations/{id}/threads`. Follow `_links.next.href` when present ([pagination](https://developer.helpscout.com/mailbox-api/overview/pagination/)). Use this endpoint rather than `embed=threads`, which returns chat threads truncated.

Help Scout's rate limit depends on your plan and is shared by all users on the account. Write requests count double, and a `429` response carries `X-RateLimit-Retry-After` ([rate limiting](https://developer.helpscout.com/mailbox-api/overview/rate-limiting/)).

## 3. Map the fields

| Dimension Labs | Help Scout | Notes |
|---|---|---|
| `text` | thread `body` | Can contain HTML: strip it. Skip `lineitem` threads (status changes, no body) and `note` threads (internal notes), and threads whose `state` is `draft`. |
| `userId` | conversation `primaryCustomer.id` | Help Scout's internal customer id, on both their threads and your replies. Fall back to the `sessionId`. Don't use `primaryCustomer.email`. |
| `incoming` | thread `createdBy.type` | `true` when it is `customer`. Other values are `user` and, in v3, `system_user` for AI-powered system users. |
| `sessionId` | conversation `id` | Prefix it, for example `helpscout-10`. |
| `conversationId` | conversation `id` | Same value as `sessionId`. |
| `dimensionlabs_timestamp` | thread `createdAt` | Convert with `toMillis`. |
| `platformJson` | thread `type`, `source`, `rating`; conversation `number`, `mailboxId`, `status`, `tags`, `customFields` | `customFields` such as account type help with segmenting. |

**Personal data.** Email and chat threads hold names, signatures and phone numbers: redact them before you send. See [PII redaction](/data/pii-redaction).

## 4. Send it

```js
// help-scout.mjs. Environment: HELPSCOUT_APP_ID, HELPSCOUT_APP_SECRET, DL_API_KEY.


const HOUR = 60 * 60 * 1000;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const stripHtml = (html) => html.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
const iso = (ms) => new Date(ms).toISOString().replace(/\.\d{3}Z$/, 'Z');

async function token() {
  const body = new URLSearchParams({ grant_type: 'client_credentials', client_id: process.env.HELPSCOUT_APP_ID, client_secret: process.env.HELPSCOUT_APP_SECRET });
  const res = await fetch('https://api.helpscout.net/v2/oauth2/token', { method: 'POST', body });
  if (!res.ok) throw new Error(`Help Scout token request returned ${res.status}: ${await res.text()}`);
  return (await res.json()).access_token;
}

// Sends every thread written at or after `since` (Unix ms). Returns the new `since`.
export async function syncHelpScout({ since, historical = false }) {
  const headers = { Authorization: `Bearer ${await token()}` }; // valid for 2 days, so one per run is enough
  // Retries Help Scout's rate limits (429) and server errors (5xx), honoring X-RateLimit-Retry-After.
  const get = async (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(`Help Scout returned ${res.status}: ${await res.text()}`);
      await sleep((Number(res.headers.get('x-ratelimit-retry-after')) || 2 ** attempt) * 1000);
    }
  };
  let newest = since;
  for (let url = `https://api.helpscout.net/v3/conversations?status=all&modifiedSince=${iso(since)}`; url; ) {
    const page = await get(url);
    for (const conv of page._embedded?.conversations ?? []) {
      for (let t = `https://api.helpscout.net/v3/conversations/${conv.id}/threads`; t; ) {
        const res = await get(t);
        for (const th of res._embedded?.threads ?? []) {
          const ts = toMillis(th.createdAt);
          const text = th.body ? stripHtml(th.body) : '';
          if (['lineitem', 'note'].includes(th.type) || th.state === 'draft' || ts < since || !text) continue;
          await sendToDimensionLabs({
            text,
            userId: conv.primaryCustomer?.id ? String(conv.primaryCustomer.id) : `helpscout-${conv.id}`,
            incoming: th.createdBy?.type === 'customer',
            sessionId: `helpscout-${conv.id}`,
            conversationId: `helpscout-${conv.id}`,
            dimensionlabs_timestamp: ts,
            platformJson: { thread_type: th.type, source: th.source?.type, rating: th.rating, number: conv.number, mailbox_id: conv.mailboxId, status: conv.status, tags: conv.tags?.map((x) => x.tag) },
          }, { historical: historical || ts < Date.now() - HOUR });
          newest = Math.max(newest, ts);
        }
        t = res._links?.next?.href;
      }
    }
    url = page._links?.next?.href;
  }
  return newest;
}
```

## 5. Run it

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

- **History first:** `node run.mjs 2025-01-01` sends every thread since that date to `/trackhistorical`. Then start a [backfill](/dimensions/backfill) from the **Backfill** tab.
- **Then every 15 minutes:** the scheduled job sends new threads to `/track`, and they are enriched automatically every day. A thread 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 conversations (check it with the [file checker](/data/check-a-file)), then run a backfill.

## Help Scout reference

- [Authentication](https://developer.helpscout.com/mailbox-api/overview/authentication/)
- [List Conversations (v3)](https://developer.helpscout.com/mailbox-api/endpoints/conversations/list-v3/)
- [List Threads (v3)](https://developer.helpscout.com/mailbox-api/endpoints/conversations/threads/list-v3/)
- [List Threads (v2, field reference)](https://developer.helpscout.com/mailbox-api/endpoints/conversations/threads/list/)
- [Pagination](https://developer.helpscout.com/mailbox-api/overview/pagination/)
- [Rate limiting](https://developer.helpscout.com/mailbox-api/overview/rate-limiting/)
- [Changelog](https://developer.helpscout.com/mailbox-api/changelog/)

Checked against Help Scout's documentation on 2026-10-13.
