How to Use the Help Scout API (2026): Auth, Endpoints & Examples
If you want to pull conversations into a data warehouse, sync customers from your CRM, open a ticket when an order fails, or build automation on top of your shared inbox, you'll be working with the Help Scout API. It's a clean, modern REST API — but unlike some help desks that hand you a single API key, Help Scout authenticates with OAuth2, and that's the first thing that trips developers up.
This guide walks through it in order. We'll cover what the Help Scout Mailbox API v2 is, the base URL, how OAuth2 authentication works (both the client-credentials and authorization-code flows), how to get an access token, the endpoints you'll actually use, pagination, rate limits, webhooks, and copy-paste examples in curl, Python and Node. Details are verified against Help Scout's developer docs as of June 2026 — Help Scout revises the product periodically, so treat exact figures as "confirm in the live docs." If you're new to the product itself, start with what is Help Scout; if you'd rather connect tools than write code, the best Help Scout integrations is the companion piece.
What the Help Scout Mailbox API v2 is
The Help Scout API is a RESTful, JSON-over-HTTPS API. You make standard HTTP requests — GET to read, POST to create, PUT/PATCH to update, DELETE to remove — against resource URLs, and you get JSON back. HTTPS is required; there's no plain-HTTP fallback.
The current version is v2, and it's the one to build against. One naming wrinkle worth flagging up front: Help Scout has been rebranding "Mailbox" to "Inbox" across its product and docs (you'll see "Inbox API 2.0" and "inboxes" where older material says "Mailbox API" and "mailboxes"). The underlying API is the same — the URL path is still /v2/, and request bodies still use a field called mailboxId. Most developers still search for the "Help Scout Mailbox API," so we use both terms here; just know they refer to the same v2 surface.
The base URL is a single, fixed host (there's no per-account subdomain like some help desks use):
https://api.helpscout.net/v2/
So the conversations endpoint is https://api.helpscout.net/v2/conversations, the customers endpoint is https://api.helpscout.net/v2/customers, and so on.
Help Scout API authentication: OAuth2
Here's the key difference from a typical "paste your API key" help desk: Help Scout uses OAuth2. There's no static, long-lived API key you copy from a settings page. Instead you register an app to get a client ID and client secret, then exchange those for a short-lived access token that you send on every request.
Step 1 — create an app for your credentials
Log in, go to Your Profile → My Apps, and click Create My App. You'll get an Application ID (client ID) and Application Secret (client secret). Treat the secret like a password — keep it out of client-side code, repos and logs, and store it in an environment variable or secrets manager.
There are two OAuth2 flows, and which one you pick depends on who the integration is for:
- Client Credentials — for internal integrations: a script, a sync job, or a backend service acting as your own account. This is what most people building against their own Help Scout want, and it's the simplest. No user redirect, no browser.
- Authorization Code — for apps meant to be installed by other Help Scout accounts (e.g. a public integration you distribute). The account owner is redirected to Help Scout to grant access, and you exchange the returned
codefor tokens. You also get a refresh token to renew access without sending them through the flow again.
Step 2 — get an access token (client credentials)
For an internal integration, POST your credentials to the token endpoint:
curl -X POST https://api.helpscout.net/v2/oauth2/token \
--data "grant_type=client_credentials" \
--data "client_id=YOUR_APP_ID" \
--data "client_secret=YOUR_APP_SECRET"
The response is a JSON object with the token and its lifetime in seconds:
{
"token_type": "bearer",
"access_token": "369dbb08be58430086d2f8bd832bc1eb",
"expires_in": 172800
}
That expires_in of 172,800 seconds is two days. When the token expires, requests start returning HTTP 401 — at which point you simply request a fresh token. In practice you cache the token and refresh it on a 401 (or proactively before the two days are up).
Step 3 — call the API with a Bearer token
Send the access token in the Authorization header as a Bearer token on every request:
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
https://api.helpscout.net/v2/users/me
GET /v2/users/me returns the resource owner — a handy first call to confirm your token works.
Authorization-code and refresh flows (for distributable apps)
If you're building an app for other accounts, redirect the user to Help Scout's authorize URL, then exchange the returned code:
curl -X POST https://api.helpscout.net/v2/oauth2/token \
--data "code=THE_RETURNED_CODE" \
--data "client_id=YOUR_APP_ID" \
--data "client_secret=YOUR_APP_SECRET" \
--data "grant_type=authorization_code"
This returns both an access_token and a refresh_token. When the access token expires, swap the refresh token for a new pair without another redirect:
curl -X POST https://api.helpscout.net/v2/oauth2/token \
--data "refresh_token=YOUR_REFRESH_TOKEN" \
--data "client_id=YOUR_APP_ID" \
--data "client_secret=YOUR_APP_SECRET" \
--data "grant_type=refresh_token"
The endpoints you'll actually use
Help Scout exposes a resource for nearly every object in the product. The ones you'll reach for most:
- Conversations —
/v2/conversations. The center of gravity for almost every integration: create, read, update, list and delete conversations (Help Scout's term for a ticket/email thread). - Threads —
/v2/conversations/{id}/threads. A thread is an individual message within a conversation — a customer message, a reply, or an internal note. This is how you post a response or note programmatically. - Customers —
/v2/customers. The people you support, with their emails, phones, social handles and addresses. Create and sync them from your own systems. - Inboxes (mailboxes) —
/v2/mailboxes. Your shared inboxes, plus their custom fields and saved replies. You'll need an inbox'sidto create conversations. - Users —
/v2/users. Your team members — useful for routing and reporting. - Reports —
/v2/reports/.... Company, conversations, productivity, happiness and user metrics for analytics pipelines.
A quick map of HTTP verbs to common actions:
| Action | Method | Example endpoint |
|---|---|---|
| List conversations | GET | /v2/conversations |
| Get one conversation | GET | /v2/conversations/{id} |
| Create a conversation | POST | /v2/conversations |
| Update a conversation | PATCH | /v2/conversations/{id} |
| Add a reply/note (thread) | POST | /v2/conversations/{id}/threads |
| List customers | GET | /v2/customers |
| Get current user | GET | /v2/users/me |
Example: create a conversation with curl
Creating a conversation requires a few fields: a subject, a customer (identified by id or email), the mailboxId of the inbox it belongs to, a type (email, chat or phone), a status (active, closed or pending), and at least one thread in the threads array.
curl -X POST https://api.helpscout.net/v2/conversations \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"subject": "Payment failed at checkout",
"customer": { "email": "[email protected]" },
"mailboxId": 85,
"type": "email",
"status": "active",
"threads": [
{
"type": "customer",
"customer": { "email": "[email protected]" },
"text": "Customer reports a 502 when paying with card."
}
]
}'
A successful create returns HTTP 201 with no body — the new conversation's ID comes back in the response headers: a Resource-ID header and a Location header (https://api.helpscout.net/v2/conversations/{id}). Read those rather than expecting a JSON payload.
Example: list conversations (Python)
Here's a minimal Python script using requests. It fetches a token, then reads a page of conversations. By default the list endpoint returns active conversations sorted by createdAt (newest first); pass status, sortField, sortOrder, mailbox, or a query to filter.
import os
import requests
CLIENT_ID = os.environ["HELPSCOUT_CLIENT_ID"]
CLIENT_SECRET = os.environ["HELPSCOUT_CLIENT_SECRET"]
BASE = "https://api.helpscout.net/v2"
# 1) Get an access token (client credentials)
tok = requests.post(f"{BASE}/oauth2/token", data={
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
})
tok.raise_for_status()
token = tok.json()["access_token"]
# 2) Call the API with a Bearer token
headers = {"Authorization": f"Bearer {token}"}
resp = requests.get(f"{BASE}/conversations",
headers=headers,
params={"status": "active", "page": 1})
resp.raise_for_status()
data = resp.json()
convos = data["_embedded"]["conversations"]
print(f"Page {data['page']['number']} of {data['page']['totalPages']} "
f"— {data['page']['totalElements']} total")
for c in convos:
print(c["id"], c.get("subject"), "|", c.get("status"))
Example: create a conversation (Node)
The same idea in Node using the built-in fetch (Node 18+):
const BASE = "https://api.helpscout.net/v2";
// 1) Get a token
const tokRes = await fetch(`${BASE}/oauth2/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: process.env.HELPSCOUT_CLIENT_ID,
client_secret: process.env.HELPSCOUT_CLIENT_SECRET,
}),
});
const { access_token } = await tokRes.json();
// 2) Create a conversation
const res = await fetch(`${BASE}/conversations`, {
method: "POST",
headers: {
"Authorization": `Bearer ${access_token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
subject: "API test conversation",
customer: { email: "[email protected]" },
mailboxId: 85,
type: "email",
status: "active",
threads: [{ type: "customer",
customer: { email: "[email protected]" },
text: "Created from Node." }],
}),
});
if (!res.ok) throw new Error(`Help Scout error ${res.status}: ${await res.text()}`);
console.log("Created conversation:", res.headers.get("Resource-ID"));
Pagination
Help Scout list endpoints return results in HAL (Hypertext Application Language) format. Two properties matter:
_embeddedholds the actual records — e.g._embedded.conversationsis the array of conversation objects._linksholds navigation URLs —self,first,last, andnext(when there's a further page).
There's also a page object summarizing the result set:
{
"_embedded": { "conversations": [ /* ... */ ] },
"_links": {
"self": { "href": "https://api.helpscout.net/v2/conversations?page=1" },
"next": { "href": "https://api.helpscout.net/v2/conversations?page=2" },
"first": { "href": "https://api.helpscout.net/v2/conversations?page=1" },
"last": { "href": "https://api.helpscout.net/v2/conversations?page=4" }
},
"page": { "number": 1, "size": 25, "totalElements": 100, "totalPages": 4 }
}
The default page size is 25. The cleanest way to walk results is to follow the _links.next.href URL until it's no longer present — that's the whole point of HAL. Alternatively, increment the page query parameter up to page.totalPages.
Rate limits
Help Scout's rate limit is plan-tiered, not a flat figure — and it's per account, shared across all tokens and apps tied to that account (it's not per token or per IP, so multiple integrations on one account draw from the same pool). The current per-minute limits are roughly:
| Plan | Requests per minute (per account) |
|---|---|
| Standard | ~200/min |
| Plus | ~400/min |
| Pro | ~800/min |
The catch most people miss: write requests count double. A POST, PUT, PATCH or DELETE counts as 2 requests toward the per-minute limit, while a GET counts as 1. So a job that creates conversations burns through the budget twice as fast as a read-only sync.
Help Scout also returns headers on responses so you can throttle proactively rather than wait to be cut off: X-RateLimit-Limit-Minute (your per-minute ceiling) and X-RateLimit-Remaining-Minute (how many requests you have left in the current window). Watch the remaining count and slow down before you hit zero.
When you do exceed the limit, Help Scout returns HTTP 429 with an X-RateLimit-Retry-After header telling you how many seconds to wait. The correct pattern is to back off for exactly that long, then retry — ideally with exponential backoff and jitter if you run many workers. Build this in from day one; it's the single most common thing that breaks naive integrations at scale. (Confirm current limits in Help Scout's rate-limiting docs, as they can vary by plan and over time.)
Webhooks
If you want to react to events rather than poll for them, use webhooks (/v2/webhooks). You register an endpoint URL and subscribe to events — conversation created, conversation assigned, customer created, and so on — and Help Scout POSTs a JSON payload to your URL when they happen. Each request is signed so you can verify it genuinely came from Help Scout. Webhooks are the right call for near-real-time sync; reserve polling for backfills and reconciliation, where they keep you well inside the rate limit.
When you'd reach for an AI layer instead of the raw API
The API is the right tool when you want deterministic, programmatic control — syncing data, wiring Help Scout into your own stack, or creating conversations from other systems. But a growing share of "API projects" are really "I want conversations triaged and answered automatically," which is a different shape of problem. Writing and maintaining that logic — classify the message, look up the customer, draft a reply, decide whether it's safe to send — is a real engineering commitment, and it gets brittle as your product and policies change.
That's the gap an AI agent layer fills. To be upfront: Macha is an AI agent layer that runs on top of Zendesk and Freshdesk, and it does not currently integrate with Help Scout — so if you're on Help Scout, this isn't a drop-in option today. We mention it only because the pattern is relevant: rather than scripting triage-and-reply against an API by hand, an agent layer reads from your help desk plus a connected knowledge base to triage, draft and resolve routine conversations, leaving anything it can't confidently handle as a normal ticket for a human. (We built this on Zendesk first — see Macha on Zendesk.) The honest trade-offs are the same everywhere: it's another integration to configure, it's only as good as the knowledge you feed it, and billing is per AI action — each automated step — not per closed ticket. If and when you're on a supported help desk, you can try it free — 7-day free trial, no credit card required. For Help Scout specifically, the right path today is the API above or an off-the-shelf connector from the best Help Scout integrations.
Frequently asked questions
What is the Help Scout API base URL? Every request goes to a single host: https://api.helpscout.net/v2/. So conversations live at https://api.helpscout.net/v2/conversations. Unlike some help desks, there's no per-account subdomain.
Does Help Scout use an API key or OAuth? OAuth2 — there's no static API key to copy. You register an app under Your Profile → My Apps to get a client ID and secret, then exchange them for a short-lived access token that you send as Authorization: Bearer {token} on every request.
How do I get a Help Scout access token? For an internal integration, POST to https://api.helpscout.net/v2/oauth2/token with grant_type=client_credentials, your client_id and client_secret. You get back an access_token valid for about two days (expires_in: 172800); request a new one when calls start returning 401. For apps used by other accounts, use the authorization-code flow and renew with the refresh token.
What's the difference between the "Mailbox API" and "Inbox API"? They're the same thing. Help Scout is rebranding "Mailbox" to "Inbox" in its product and docs, but the API path is still /v2/ and request bodies still use mailboxId. Search results use both names.
What are the Help Scout API rate limits? The limit is tiered by plan, per account (shared across all of that account's tokens and apps): roughly ~200/min on Standard, ~400/min on Plus, and ~800/min on Pro. Write requests (POST/PUT/PATCH/DELETE) count as 2 each; reads count as 1. Use the X-RateLimit-Limit-Minute and X-RateLimit-Remaining-Minute response headers to throttle proactively, and on a 429, wait the number of seconds in the X-RateLimit-Retry-After header before retrying. Confirm current figures in Help Scout's docs.
How do I paginate Help Scout API results? Responses use HAL: records are in _embedded, and navigation URLs are in _links. Follow _links.next.href until it's absent, or increment the page parameter up to page.totalPages. The default page size is 25.
The bottom line
The Help Scout Mailbox (Inbox) API v2 is straightforward once the OAuth piece clicks: every call goes to https://api.helpscout.net/v2/, you authenticate by exchanging an app's client ID and secret for a two-day Bearer access token, and you work with JSON resources for conversations, threads, customers, inboxes and users. The three things that catch people are the OAuth flow (there's no static API key), the HAL pagination (follow _links.next), and the rate limit (plan-tiered per account — ~200/min on Standard, ~400/min on Plus, ~800/min on Pro — with writes counting double). Cache your token, handle 401 by refreshing it, handle 429 by honoring X-RateLimit-Retry-After, and you can sync, create and update almost anything in your inbox. From here, see what Help Scout is for the product overview, or the best Help Scout integrations if you'd rather connect a tool than write code.
Help Scout Mailbox API v2 details verified against Help Scout's developer documentation, June 2026. Help Scout updates its API periodically — confirm endpoints, limits and field values in the live docs before relying on them.
Resolve tickets automatically with AI agents
Macha's AI agents work on top of the help desk you already use — no code.
Zendesk
Freshdesk
Gorgias
Front
Shopify
Stripe
Slack
Notion
Google Workspace
Confluence

