02 Get your data in /Integrations
Help Scout
Send Help Scout conversations and their threads to Dimension Labs with the universal API.
- HS Help ScoutTickets, chats, calls, reviews
- ⟳ Your jobPull new · map fields · every 15 min
- { } Universal APIPOST /track · /trackhistorical
- Dimension LabsRead in full · enriched daily
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.
What you need
Section titled “What you need”- An active, invited Help Scout user to own the app. Help Scout returns
401for 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.
1. Get API access in Help Scout
Section titled “1. Get API access in Help Scout”- Go to Your Profile > My apps and click Create My App.
- Help Scout asks for a redirection URL. For the client credentials flow any valid URL works, such as
https://example.com. - Copy the application ID and application secret.
- Request an access token (authentication):
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
Section titled “2. Pull new conversations”Use List Conversations (v3), which Help Scout recommends over v2 for performance, with modifiedSince. Add status=all, because only active conversations are listed by default.
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): GET /v3/conversations/{id}/threads. Follow _links.next.href when present (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).
3. Map the fields
Section titled “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.
4. Send it
Section titled “4. Send it”// help-scout.mjs. Environment: HELPSCOUT_APP_ID, HELPSCOUT_APP_SECRET, DL_API_KEY.import { sendToDimensionLabs, toMillis } from './dimensionlabs.mjs';
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
Section titled “5. Run it”Save the code as help-scout.mjs in a project set up as in Set up your project, and run it with the scheduled sync runner: import { syncHelpScout as sync } from './help-scout.mjs';.
- History first:
node run.mjs 2025-01-01sends every thread since that date to/trackhistorical. Then start a 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 of exported conversations (check it with the file checker), then run a backfill.
Help Scout reference
Section titled “Help Scout reference”- Authentication
- List Conversations (v3)
- List Threads (v3)
- List Threads (v2, field reference)
- Pagination
- Rate limiting
- Changelog
Checked against Help Scout’s documentation on 2026-10-13.