Quick reference for Intercom API errors by HTTP status code, with real error response shapes and proven solutions.
All Intercom errors return this structure:
{
"type": "error.list",
"request_id": "req_abc123",
"errors": [
{
"code": "unauthorized",
"message": "Access Token Invalid"
}
]
}
{
"type": "error.list",
"errors": [{ "code": "unauthorized", "message": "Access Token Invalid" }]
}
Causes:
Fix:
# Verify token works
curl -s https://api.intercom.io/me \
-H "Authorization: Bearer $INTERCOM_ACCESS_TOKEN" \
-H "Accept: application/json" | jq '.type'
# Should return "admin"
# If invalid, regenerate at:
# app.intercom.com > Settings > Developer Hub > Your App > Authentication
{
"type": "error.list",
"errors": [{ "code": "forbidden", "message": "You do not have permission to access this resource" }]
}
Causes:
Fix: Add the required OAuth scope in Developer Hub > OAuth Scopes.
{
"type": "error.list",
"errors": [{ "code": "not_found", "message": "User Not Found" }]
}
Causes:
user_id where contact_id is expected (or vice versa)Fix:
// Always check existence before operating
try {
const contact = await client.contacts.find({ contactId: id });
} catch (err) {
if (err instanceof IntercomError && err.statusCode === 404) {
console.log(`Contact ${id} not found, skipping`);
}
}
{
"type": "error.list",
"errors": [{ "code": "conflict", "message": "A contact matching those details already exists with id=abc123" }]
}
Causes:
external_id or email
Fix:
// Search first, create if not found
async function findOrCreateContact(email: string, externalId: string) {
const existing = await client.contacts.search({
query: { field: "email", operator: "=", value: email },
});
if (existing.data.length > 0) {
return existing.data[0];
}
return client.contacts.create({
role: "user",
email,
externalId,
});
}
{
"type": "error.list",
"errors": [{ "code": "parameter_invalid", "message": "email is not a valid email address" }]
}
Causes:
Fix: Validate inputs before sending. Check the errors array for specifics.
{
"type": "error.list",
"errors": [{ "code": "rate_limit_exceeded", "message": "Rate limit exceeded" }]
}
Response headers:
X-RateLimit-Limit: 10000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1711100060
Limits: 10,000 req/min per app, 25,000 req/min per workspace.
Fix:
import { IntercomError } from "intercom-client";
async function withBackoff<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (err) {
if (err instanceof IntercomError && err.statusCode === 429) {
if (attempt === maxRetries) throw err;
const resetAt = err.headers?.["x-ratelimit-reset"];
const waitMs = resetAt
? (parseInt(resetAt) * 1000) - Date.now() + 1000
: 1000 * Math.pow(2, attempt);
console.log(`Rate limited, waiting ${waitMs}ms`);
await new Promise(r => setTimeout(r, Math.max(waitMs, 1000)));
} else {
throw err;
}
}
}
throw new Error("Unreachable");
}
Causes: Intercom-side issue, not your fault.
Fix:
# 1. Check Intercom status
curl -s https://status.intercom.com/api/v2/summary.json | jq '.status'
# 2. Retry with backoff (same pattern as 429)
# 3. If persistent, contact Intercom support with request_id
#!/bin/bash
TOKEN="${INTERCOM_ACCESS_TOKEN}"
echo "=== Intercom API Diagnostics ==="
# Test auth
echo -n "Auth: "
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer $TOKEN" \
https://api.intercom.io/me)
echo "$STATUS $([ "$STATUS" = "200" ] && echo "OK" || echo "FAIL")"
# Check rate limits
echo -n "Rate limit remaining: "
curl -s -D - -o /dev/null \
-H "Authorization: Bearer $TOKEN" \
https://api.intercom.io/me 2>/dev/null | grep -i x-ratelimit-remaining
# Intercom status
echo -n "Intercom status: "
curl -s https://status.intercom.com/api/v2/status.json | jq -r '.status.description'
| Error Code | HTTP | Retryable | Action |
|---|---|---|---|
unauthorized |
401 | No | Regenerate token |
forbidden |
403 | No | Add OAuth scope |
not_found |
404 | No | Verify resource ID |
conflict |
409 | No | Search before create |
parameter_invalid |
422 | No | Fix input data |
rate_limit_exceeded |
429 | Yes | Backoff and retry |
server_error |
500+ | Yes | Retry, check status page |
For comprehensive debugging, see intercom-debug-bundle.