Stitchflow
Intercom logo

Intercom User Management API Guide

API workflow

How to automate user lifecycle operations through APIs with caveats that matter in production.

UpdatedMar 9, 2026

Summary and recommendation

Intercom exposes two distinct API surfaces that serve different identity purposes and must not be conflated. The REST API at https://api.intercom.io manages contacts (end-users and leads) via /contacts and provides read-only access to admins (teammates) via /admins.

Teammate provisioning and deprovisioning is handled exclusively through the SCIM 2.0 API at https://api.intercom.io/scim/v2, which requires the Expert (Enterprise) plan and a separately generated SCIM token. Auth for the REST API uses Bearer token (server-side) or OAuth 2.0 (third-party integrations); SCIM uses its own Bearer token issued from the Security settings panel.

Building an accurate identity graph across both surfaces requires joining /admins (teammate records) with /contacts (user records) using shared email as the correlation key, since Intercom does not expose a unified identity object spanning both types.

API quick reference

Has user APIYes
Auth methodBearer token (Access Token) for server-side integrations; OAuth 2.0 for third-party app integrations
Base URLOfficial docs
SCIM availableYes
SCIM plan requiredExpert (Enterprise)

Authentication

Auth method: Bearer token (Access Token) for server-side integrations; OAuth 2.0 for third-party app integrations

Setup steps

  1. Go to the Intercom Developer Hub (https://app.intercom.com/a/developer-signup) and create or open an app.
  2. For direct API access: navigate to 'Authentication' in your app settings and copy the Access Token; include it as 'Authorization: Bearer ' on every request.
  3. For OAuth 2.0: register redirect URIs, implement the authorization code flow, exchange the code for an access token, and store the token securely.
  4. Set the 'Accept: application/json' and 'Content-Type: application/json' headers on all requests.
  5. Scope permissions are configured per-app in the Developer Hub under 'Permissions'.

Required scopes

Scope Description Required for
Read contacts Allows reading contact (user/lead) records. GET /contacts, GET /contacts/{id}, POST /contacts/search
Write contacts Allows creating, updating, and deleting contact records. POST /contacts, PUT /contacts/{id}, DELETE /contacts/{id}, POST /contacts/merge
Read conversations Allows reading conversation data associated with users. GET /conversations
Read admins Allows reading admin (teammate) records. GET /admins, GET /admins/{id}
Read tags Allows reading tags applied to contacts. GET /tags
Write tags Allows creating tags and tagging/untagging contacts. POST /tags, POST /contacts/{id}/tags

User object / data model

Field Type Description On create On update Notes
id string Intercom-generated unique contact ID. auto-generated immutable Use this ID for all subsequent API calls.
external_id string Your application's unique identifier for the contact. optional updatable Recommended for deduplication; used to match existing contacts.
email string Contact's email address. optional (required if no external_id) updatable Used for deduplication. Duplicate emails across contacts will be merged.
role string (enum) Contact role: 'user' or 'lead'. required updatable Determines contact type. 'user' requires email or external_id.
name string Full name of the contact. optional updatable
phone string Phone number in E.164 format. optional updatable
avatar object Avatar image URL for the contact. optional updatable Contains 'image_url' field.
signed_up_at integer (Unix timestamp) Timestamp when the user signed up. optional updatable
last_seen_at integer (Unix timestamp) Timestamp of last seen activity. optional updatable
last_replied_at integer (Unix timestamp) Timestamp of last reply from contact. read-only read-only
unsubscribed_from_emails boolean Whether the contact has unsubscribed from emails. optional updatable
location object Location data including city, country, region. read-only (auto-detected) read-only Populated automatically from IP; not directly settable.
custom_attributes object (key-value) Custom data attributes defined in your Intercom workspace. optional updatable Keys must be pre-defined in Intercom settings. Values can be string, number, boolean, or date.
tags object (list) Tags associated with the contact. read-only (manage via /tags endpoint) read-only (manage via /tags endpoint) Use POST /contacts/{id}/tags to add/remove tags.
companies object (list) Companies the contact is associated with. optional via companies array updatable via /contacts/{id}/companies Each entry references a company by company_id.
workspace_id string Intercom workspace the contact belongs to. auto-assigned immutable
created_at integer (Unix timestamp) Timestamp when the contact was created in Intercom. auto-generated immutable
updated_at integer (Unix timestamp) Timestamp of last update to the contact. auto-generated auto-updated

Core endpoints

Create Contact

  • Method: POST
  • URL: https://api.intercom.io/contacts
  • Watch out for: If a contact with the same email already exists, Intercom may return the existing contact rather than creating a duplicate. Always check the returned 'id'.

Request example

POST /contacts
Authorization: Bearer <token>
Content-Type: application/json
{
  "role": "user",
  "email": "jane@example.com",
  "name": "Jane Doe",
  "external_id": "usr_001"
}

Response example

{
  "type": "contact",
  "id": "64a1b2c3d4e5f6a7b8c9d0e1",
  "role": "user",
  "email": "jane@example.com",
  "name": "Jane Doe",
  "external_id": "usr_001",
  "created_at": 1720000000
}

Retrieve Contact

  • Method: GET
  • URL: https://api.intercom.io/contacts/{id}
  • Watch out for: Use Intercom's internal 'id', not 'external_id', in the URL path. To look up by external_id or email, use the Search endpoint.

Request example

GET /contacts/64a1b2c3d4e5f6a7b8c9d0e1
Authorization: Bearer <token>

Response example

{
  "type": "contact",
  "id": "64a1b2c3d4e5f6a7b8c9d0e1",
  "role": "user",
  "email": "jane@example.com",
  "name": "Jane Doe",
  "custom_attributes": {"plan": "pro"}
}

Update Contact

  • Method: PUT
  • URL: https://api.intercom.io/contacts/{id}
  • Watch out for: Only include fields you want to change; omitted fields retain their current values. Custom attribute keys must be pre-defined in the workspace.

Request example

PUT /contacts/64a1b2c3d4e5f6a7b8c9d0e1
Authorization: Bearer <token>
Content-Type: application/json
{
  "name": "Jane Smith",
  "custom_attributes": {"plan": "enterprise"}
}

Response example

{
  "type": "contact",
  "id": "64a1b2c3d4e5f6a7b8c9d0e1",
  "name": "Jane Smith",
  "custom_attributes": {"plan": "enterprise"}
}

Delete Contact

  • Method: DELETE
  • URL: https://api.intercom.io/contacts/{id}
  • Watch out for: Deletion is permanent and removes all associated conversation history linkage. Consider archiving instead if data retention is needed.

Request example

DELETE /contacts/64a1b2c3d4e5f6a7b8c9d0e1
Authorization: Bearer <token>

Response example

{
  "type": "contact_deleted",
  "id": "64a1b2c3d4e5f6a7b8c9d0e1",
  "deleted": true
}

List Contacts

  • Method: GET
  • URL: https://api.intercom.io/contacts
  • Watch out for: List endpoint returns all contacts without filtering. For filtered retrieval, use the Search endpoint. Cursor-based pagination; do not use page offsets.

Request example

GET /contacts?per_page=50
Authorization: Bearer <token>

Response example

{
  "type": "list",
  "data": [{"id": "...", "email": "..."}],
  "pages": {
    "next": {"starting_after": "cursor_token_xyz"}
  },
  "total_count": 1200
}

Search Contacts

  • Method: POST
  • URL: https://api.intercom.io/contacts/search
  • Watch out for: Search has a lower rate limit than standard endpoints. Complex nested queries using 'AND'/'OR' operators are supported but increase response time.

Request example

POST /contacts/search
Authorization: Bearer <token>
Content-Type: application/json
{
  "query": {
    "field": "email",
    "operator": "=",
    "value": "jane@example.com"
  }
}

Response example

{
  "type": "list",
  "data": [{"id": "64a1b2c3d4e5f6a7b8c9d0e1", "email": "jane@example.com"}],
  "total_count": 1
}

Merge Contacts

  • Method: POST
  • URL: https://api.intercom.io/contacts/merge
  • Watch out for: Merge is irreversible. The 'from' contact (typically a lead) is deleted; its conversations are transferred to the 'into' contact (must be a user).

Request example

POST /contacts/merge
Authorization: Bearer <token>
Content-Type: application/json
{
  "from": "lead_contact_id_abc",
  "into": "user_contact_id_xyz"
}

Response example

{
  "type": "contact",
  "id": "user_contact_id_xyz",
  "role": "user",
  "email": "jane@example.com"
}

List Admins (Teammates)

  • Method: GET
  • URL: https://api.intercom.io/admins
  • Watch out for: Admins (teammates) are managed separately from contacts. There is no API endpoint to create or delete admins; admin management is done via the Intercom UI or SCIM.

Request example

GET /admins
Authorization: Bearer <token>

Response example

{
  "type": "admin.list",
  "admins": [
    {"type": "admin", "id": "1234567", "name": "Alice", "email": "alice@company.com"}
  ]
}

Rate limits, pagination, and events

  • Rate limits: Intercom enforces per-app rate limits based on plan. Limits apply per access token per second and per minute. Rate limit headers are returned on every response.
  • Rate-limit headers: Yes
  • Retry-After header: Yes
  • Rate-limit notes: Headers returned: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (Unix timestamp). When limit is exceeded, API returns HTTP 429. Retry-After header is included in 429 responses. Bulk search and scroll endpoints have separate, lower limits.
  • Pagination method: cursor
  • Default page size: 50
  • Max page size: 150
  • Pagination pointer: starting_after (cursor value from pages.next.starting_after in response)
Plan Limit Concurrent
Essential / Advanced (non-Enterprise) 83 requests/second (approx. 5,000 requests/minute) 0
Expert / Enterprise 167 requests/second (approx. 10,000 requests/minute) 0
  • Webhooks available: Yes
  • Webhook notes: Intercom supports webhooks (called 'Notification Subscriptions') that send HTTP POST payloads to a configured URL when specified events occur. Webhooks are configured per-app in the Developer Hub.
  • Alternative event strategy: For polling-based sync, use GET /contacts with cursor pagination and filter by updated_at.
  • Webhook events: contact.created, contact.signed_up, contact.added_email, contact.tag.created, contact.tag.deleted, conversation.user.created, conversation.user.replied, conversation.admin.replied, conversation.admin.closed, conversation.admin.assigned, user.created, user.deleted, user.email.updated, user.unsubscribed

SCIM API status

  • SCIM available: Yes

  • SCIM version: 2.0

  • Plan required: Expert (Enterprise)

  • Endpoint: https://api.intercom.io/scim/v2

  • Supported operations: GET /scim/v2/Users – List provisioned users, GET /scim/v2/Users/{id} – Retrieve a user, POST /scim/v2/Users – Provision a new user (admin/teammate), PUT /scim/v2/Users/{id} – Replace user attributes, PATCH /scim/v2/Users/{id} – Update user attributes (activate/deactivate), DELETE /scim/v2/Users/{id} – Deprovision a user, GET /scim/v2/Groups – List groups (teams), POST /scim/v2/Groups – Create a group, PATCH /scim/v2/Groups/{id} – Update group membership, DELETE /scim/v2/Groups/{id} – Delete a group

Limitations:

  • SCIM provisions Intercom admins (teammates), not end-user contacts.
  • SSO must be configured and enabled before SCIM provisioning can be activated.
  • Requires Expert (Enterprise) plan; not available on Essential or Advanced.
  • Supported IdPs: Okta, Microsoft Entra ID (Azure AD), OneLogin.
  • SCIM token is generated separately from the REST API access token.
  • Deprovisioning via SCIM deactivates the admin but does not delete conversation history.
  • Group (team) management via SCIM has limited attribute support compared to the UI.

Common scenarios

Three integration patterns cover the majority of production use cases. For SaaS sign-up sync, POST /contacts with role='user', email, external_id, and custom_attributes on registration; store the returned Intercom id and use PUT /contacts/{id} for subsequent profile updates.

For bulk attribute updates, POST /contacts/search with a custom_attribute filter, paginate results via the starting_after cursor, and update each record - note that search carries a stricter rate limit than GET /contacts, so implement exponential backoff on HTTP 429 and respect the Retry-After header.

For teammate lifecycle automation via SCIM with Okta, configure the SCIM base URL and Bearer token in the Okta Integration Network app, map userName to email, and rely on Okta group assignment to trigger POST /scim/v2/Users.

deprovisioning sends PATCH with active=false rather than DELETE, preserving conversation history but leaving open conversations unassigned.

Sync new SaaS sign-ups to Intercom as users

  1. On user registration in your app, call POST /contacts with role='user', email, external_id, and relevant custom_attributes (e.g., plan, company).
  2. Store the returned Intercom 'id' in your database alongside the user record.
  3. On subsequent profile updates (e.g., plan upgrade), call PUT /contacts/{id} with updated fields.
  4. To associate the user with a company, call POST /contacts/{id}/companies with the company_id.

Watch out for: If the user already exists (matched by email), Intercom returns the existing contact. Always use the returned 'id' rather than assuming a new record was created.

Bulk-search and update contacts by custom attribute

  1. Call POST /contacts/search with a query filtering on the target custom_attribute field and value.
  2. Iterate through paginated results using the 'starting_after' cursor from pages.next.
  3. For each contact, call PUT /contacts/{id} with the updated custom_attributes.
  4. Implement exponential backoff on HTTP 429 responses, respecting the Retry-After header.

Watch out for: Search has a lower rate limit than standard GET endpoints. For large datasets (>10,000 contacts), consider using the Intercom bulk export feature or Data Export API instead of repeated search calls.

Provision and deprovision teammates via SCIM with Okta

  1. Ensure the workspace is on the Expert (Enterprise) plan and SSO is configured.
  2. In Intercom settings, navigate to Security > SCIM Provisioning and generate a SCIM token.
  3. In Okta, add the Intercom app from the Okta Integration Network and configure SCIM base URL (https://api.intercom.io/scim/v2) and Bearer token.
  4. Map Okta user attributes to SCIM attributes (userName → email, givenName, familyName).
  5. Assign users/groups in Okta to trigger POST /scim/v2/Users provisioning in Intercom.
  6. To deprovision, unassign the user in Okta; Okta sends PATCH /scim/v2/Users/{id} with active=false, deactivating the admin in Intercom.

Watch out for: Deprovisioning deactivates the admin account but does not delete conversation history or reassign open conversations. Manually reassign conversations before deprovisioning to avoid orphaned workloads.

Why building this yourself is a trap

Several API behaviors produce silent failures or data integrity issues if not handled explicitly. external_id is immutable once set - plan the ID strategy before any bulk import, as there is no API path to correct it afterward.

Custom attribute keys must be pre-defined in workspace settings; POSTing an undefined key does not create it and the value is silently dropped. Contact deduplication by email means POST /contacts may return an existing record rather than a new one - always use the returned id, never assume creation.

Rate limits are pooled per access token across all integrations sharing a workspace, not per integration, so a high-volume integration can starve others. Cursor expiry on /contacts pagination means long-running sync jobs must not cache cursors across sessions.

Webhook payloads are signed with HMAC-SHA1 in the X-Hub-Signature header; skipping signature verification exposes the integration to spoofed events. Finally, SCIM deprovisioning deactivates the admin account but does not reassign open conversations - orphaned workloads must be handled out-of-band before the PATCH is sent.

Automate Intercom workflows without one-off scripts

Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.

Every app coverage, including apps without APIs
60+ app integrations plus browser automation for apps without APIs
IT graph reconciliation across apps and your IdP
Less than a week to launch, maintained as APIs and admin consoles change
SOC 2 Type II. ~2 hours of your team's time

UpdatedMar 9, 2026

* Details sourced from official product documentation and admin references.

Keep exploring

Related apps

15Five logo

15Five

Full API + SCIM
AutomationAPI + SCIM
Last updatedFeb 2026

15Five uses a fixed role-based permission model with six predefined roles: Account Admin, HR Admin, Billing Admin, Group Admin, Manager, and Employee. No custom roles can be constructed. User management lives at Settings gear → People → Manage people p

1Password logo

1Password

Full API + SCIM
AutomationAPI + SCIM
Last updatedFeb 2026

1Password's admin console at my.1password.com covers the full user lifecycle — invitations, group assignments, vault access, suspension, and deletion — without any third-party tooling. Like every app that mixes role-based and resource-level permissions

8x8 logo

8x8

Full API + SCIM
AutomationAPI + SCIM
Last updatedFeb 2026

8x8 Admin Console supports full lifecycle user management — create, deactivate, and delete — across its X Series unified communications platform. Every app a user can access (8x8 Work desktop, mobile, web, Agent Workspace) is gated by license assignmen