API Reference
v1.0
https://api.supportly.io/api/v1

Introduction

The Supportly API is a RESTful interface that lets you integrate customer support into your products. Manage conversations, contacts, teams, and more programmatically.

Base URL — All API requests should be made to: https://api.supportly.io/api/v1
Quick Start
Base URL
curl "https://api.supportly.io/api/v1/accounts/1/conversations" \
  -H "api_access_token: YOUR_TOKEN"
Base URL
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/conversations")
req = Net::HTTP::Get.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
Base URL
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/conversations', {
  headers: { 'api_access_token': 'YOUR_TOKEN' }
});
const data = await resp.json();
Base URL
import requests

resp = requests.get('https://api.supportly.io/api/v1/accounts/1/conversations',
    headers={'api_access_token': 'YOUR_TOKEN'})
data = resp.json()

Authentication

Supportly supports multiple authentication methods. Include your credentials in the request header to access protected endpoints.

API Access Token

Use a user or bot token via the api_access_token header. Best for server-side integrations.

OAuth 2.0 Bearer Token

Use Authorization: Bearer TOKEN header. Best for third-party apps acting on behalf of users.

User Session Cookie

Browser session-based auth for the Supportly dashboard. Not for API integrations.

Platform App Token

Platform-level tokens for multi-account management. Used by platform app developers.

Authentication
API Token Auth
curl "https://api.supportly.io/api/v1/accounts/1/conversations" \
  -H "api_access_token: YOUR_TOKEN"
Bearer Token Auth
curl "https://api.supportly.io/api/v1/accounts/1/conversations" \
  -H "Authorization: Bearer YOUR_OAUTH_TOKEN"
API Token Auth
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/conversations")
req = Net::HTTP::Get.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
API Token Auth
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/conversations', {
  headers: { 'api_access_token': 'YOUR_TOKEN' }
});
const data = await resp.json();
API Token Auth
import requests

resp = requests.get('https://api.supportly.io/api/v1/accounts/1/conversations',
    headers={'api_access_token': 'YOUR_TOKEN'})
data = resp.json()

Rate Limits

API requests are rate-limited to ensure platform stability. Limits are applied per token on a sliding window.

Rate limit info is returned in response headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
429 Too Many Requests — If you exceed the rate limit, you'll receive a 429 response. Wait until X-RateLimit-Reset before retrying.
Rate Limit Headers
Response Headers
HTTP/1.1 200 OK
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 295
X-RateLimit-Reset: 1708003600
Rate Limited Response
429 Response
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1708003600
Retry-After: 30

{
  "errors": [
    {
      "message": "Rate limit exceeded. Please retry after 30 seconds."
    }
  ]
}

Error Handling

The API uses standard HTTP status codes. Errors return a JSON body with details about what went wrong.

200Success
201Created
400Bad Request — invalid parameters
401Unauthorized — invalid or missing credentials
403Forbidden — insufficient permissions
404Not Found — resource does not exist
422Unprocessable Entity — validation failed
429Too Many Requests — rate limit exceeded
500Internal Server Error
Error Response Format
Error Response
{
  "errors": [
    {
      "field": "email",
      "message": "has already been taken",
      "code": "taken"
    }
  ]
}

OAuth 2.0

Supportly supports the OAuth 2.0 Authorization Code flow, allowing third-party applications to securely access the API on behalf of users. Create OAuth apps in Settings → Developer Apps.

OAuth 2.0 Flow:
1. Redirect user to /oauth/authorize with your client ID and scopes.
2. User approves access on the consent screen.
3. Supportly redirects back with an authorization code.
4. Exchange code for access token via POST /oauth/token.
5. Use access token for authenticated API requests.
Available Scopes
readwriteconversations.readconversations.writecontacts.readcontacts.writemessages.readmessages.writeinboxes.readinboxes.writeteams.readteams.writelabels.readlabels.writereports.readagents.readagents.writeaccounts.readaccounts.writecampaigns.readcampaigns.writeautomations.readautomations.writecaptain.readcaptain.writewebhooks.readwebhooks.writeintegrations.readintegrations.write
OAuth 2.0 Flow Overview
Complete OAuth Flow
## Step 1: Redirect user to authorize
GET https://api.supportly.io/oauth/authorize
    ?client_id=YOUR_CLIENT_ID
    &redirect_uri=https://yourapp.com/callback
    &response_type=code
    &scope=conversations.read contacts.read

## Step 2: User approves โ†’ redirected back with code
https://yourapp.com/callback?code=AUTH_CODE&state=CSRF_TOKEN

## Step 3: Exchange code for access token
POST https://api.supportly.io/oauth/token
{
  "grant_type": "authorization_code",
  "code": "AUTH_CODE",
  "client_id": "YOUR_CLIENT_ID",
  "client_secret": "YOUR_CLIENT_SECRET",
  "redirect_uri": "https://yourapp.com/callback"
}

## Step 4: Use access token for API requests
GET https://api.supportly.io/api/v1/accounts/1/conversations
Authorization: Bearer YOUR_ACCESS_TOKEN

## Step 5: Refresh when expired
POST https://api.supportly.io/oauth/token
{ "grant_type": "refresh_token", "refresh_token": "..." }
GET/oauth/authorizeShow authorization consent page

Redirect users to this URL to begin the OAuth flow. Displays a consent page where the user can approve or deny access.

Query Parameters
client_idstringrequiredYour OAuth application's client ID
redirect_uristringrequiredURL to redirect after authorization. Must match a registered redirect URI.
response_typestringrequiredMust be code
scopestringSpace-separated list of requested scopes. Defaults to read.
statestringAn opaque value for CSRF protection. Returned unchanged in the callback.
OAuth Authorize
Example
GET https://api.supportly.io/oauth/authorize?
  client_id=your_client_id&
  redirect_uri=https://yourapp.com/callback&
  response_type=code&
  scope=conversations.read contacts.read&
  state=random_csrf_string
POST/oauth/tokenExchange code for access token

Exchange an authorization code for an access token and refresh token. The authorization code is single-use and expires in 10 minutes.

Body Parameters
grant_typestringrequiredMust be authorization_code
codestringrequiredThe authorization code received from the callback
client_idstringrequiredYour application's client ID
client_secretstringrequiredYour application's client secret
redirect_uristringrequiredMust match the redirect URI used in the authorize request
Token Exchange
Request
curl -X POST "https://api.supportly.io/oauth/token" \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "code": "AUTH_CODE_HERE",
    "client_id": "your_client_id",
    "client_secret": "your_client_secret",
    "redirect_uri": "https://yourapp.com/callback"
  }'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/oauth/token")
req = Net::HTTP::Post.new(uri)
req['Content-Type'] = 'application/json'
req.body = {
  grant_type: 'authorization_code',
  code: 'AUTH_CODE_HERE',
  client_id: 'your_client_id',
  client_secret: 'your_client_secret',
  redirect_uri: 'https://yourapp.com/callback'
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Request
const resp = await fetch('https://api.supportly.io/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    grant_type: 'authorization_code',
    code: 'AUTH_CODE_HERE',
    client_id: 'your_client_id',
    client_secret: 'your_client_secret',
    redirect_uri: 'https://yourapp.com/callback'
  })
});
const data = await resp.json();
Request
import requests

resp = requests.post('https://api.supportly.io/oauth/token', json={
    'grant_type': 'authorization_code',
    'code': 'AUTH_CODE_HERE',
    'client_id': 'your_client_id',
    'client_secret': 'your_client_secret',
    'redirect_uri': 'https://yourapp.com/callback'
})
data = resp.json()
Response (200)
{
  "access_token": "eyJhbGciOiJIUzI1NiJ9...",
  "token_type": "Bearer",
  "expires_in": 86400,
  "refresh_token": "dGhpcyBpcyBhIHJlZnJlc2g...",
  "scope": "conversations.read contacts.read",
  "created_at": 1708000000
}
POST/oauth/tokenRefresh an expired access token

Use a refresh token to obtain a new access token when the current one expires. Access tokens expire after 24 hours.

Body Parameters
grant_typestringrequiredMust be refresh_token
refresh_tokenstringrequiredThe refresh token from a previous token exchange
client_idstringrequiredYour application's client ID
client_secretstringrequiredYour application's client secret
Refresh Token
Request
curl -X POST "https://api.supportly.io/oauth/token" \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "refresh_token",
    "refresh_token": "YOUR_REFRESH_TOKEN",
    "client_id": "your_client_id",
    "client_secret": "your_client_secret"
  }'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/oauth/token")
req = Net::HTTP::Post.new(uri)
req['Content-Type'] = 'application/json'
req.body = {
  grant_type: 'refresh_token',
  refresh_token: 'YOUR_REFRESH_TOKEN',
  client_id: 'your_client_id',
  client_secret: 'your_client_secret'
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
Request
const resp = await fetch('https://api.supportly.io/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    grant_type: 'refresh_token',
    refresh_token: 'YOUR_REFRESH_TOKEN',
    client_id: 'your_client_id',
    client_secret: 'your_client_secret'
  })
});
Request
import requests

resp = requests.post('https://api.supportly.io/oauth/token', json={
    'grant_type': 'refresh_token',
    'refresh_token': 'YOUR_REFRESH_TOKEN',
    'client_id': 'your_client_id',
    'client_secret': 'your_client_secret'
})
POST/oauth/revokeRevoke an access or refresh token

Immediately invalidate an access token or refresh token. Use this when a user disconnects your app.

Body Parameters
tokenstringrequiredThe access token or refresh token to revoke
client_idstringrequiredYour application's client ID
client_secretstringrequiredYour application's client secret
Revoke Token
Request
curl -X POST "https://api.supportly.io/oauth/revoke" \
  -H "Content-Type: application/json" \
  -d '{
    "token": "TOKEN_TO_REVOKE",
    "client_id": "your_client_id",
    "client_secret": "your_client_secret"
  }'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/oauth/revoke")
req = Net::HTTP::Post.new(uri)
req['Content-Type'] = 'application/json'
req.body = { token: 'TOKEN_TO_REVOKE', client_id: 'your_client_id', client_secret: 'your_client_secret' }.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
Request
const resp = await fetch('https://api.supportly.io/oauth/revoke', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    token: 'TOKEN_TO_REVOKE',
    client_id: 'your_client_id',
    client_secret: 'your_client_secret'
  })
});
Request
import requests

resp = requests.post('https://api.supportly.io/oauth/revoke', json={
    'token': 'TOKEN_TO_REVOKE',
    'client_id': 'your_client_id',
    'client_secret': 'your_client_secret'
})

Conversations

Conversations are the core of Supportly. Each conversation represents an interaction thread between a customer and your team across any channel.

GET/api/v1/accounts/{account_id}/conversations

Retrieve a paginated list of conversations. Filter by status, assignee, inbox, team, and labels.

Query Parameters
statusstringFilter by status: open, resolved, pending, snoozed, all
assignee_typestringFilter by assignment: me, unassigned, assigned, all
inbox_idintegerFilter conversations by inbox
team_idintegerFilter conversations by team
labels[]arrayFilter by labels
pageintegerPage number for pagination (default: 1)
200Returns paginated list of conversations with metadata
401Unauthorized
POST/api/v1/accounts/{account_id}/conversations

Create a new conversation. Requires an inbox and contact to start the thread.

Body Parameters
inbox_idintegerrequiredThe inbox to create the conversation in
contact_idintegerrequiredThe contact associated with this conversation
messageobjectInitial message: { "content": "Hello!" }
assignee_idintegerAgent ID to assign the conversation to
team_idintegerTeam ID to assign the conversation to
statusstringInitial status: open, pending
GET/api/v1/accounts/{account_id}/conversations/{id}

Retrieve a single conversation by its display ID, including metadata, participants, and assignment info.

PATCH/api/v1/accounts/{account_id}/conversations/{id}

Update conversation properties like status, priority, or custom attributes.

Body Parameters
statusstringopen, resolved, pending, snoozed
prioritystringnone, low, medium, high, urgent
snoozed_untilintegerUnix timestamp for snooze end (when status is snoozed)
List Conversations
Request
curl "https://api.supportly.io/api/v1/accounts/1/conversations?status=open&page=1" \
  -H "api_access_token: YOUR_TOKEN"
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/conversations?status=open&page=1")
req = Net::HTTP::Get.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/conversations?status=open&page=1', {
  headers: { 'api_access_token': 'YOUR_TOKEN' }
});
const data = await resp.json();
Request
import requests

resp = requests.get('https://api.supportly.io/api/v1/accounts/1/conversations',
    params={'status': 'open', 'page': 1},
    headers={'api_access_token': 'YOUR_TOKEN'})
data = resp.json()
Create Conversation
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/conversations" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "inbox_id": 1,
    "contact_id": 42,
    "message": { "content": "Hello, I need help!" },
    "status": "open"
  }'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/conversations")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req.body = { inbox_id: 1, contact_id: 42, message: { content: 'Hello, I need help!' }, status: 'open' }.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/conversations', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    inbox_id: 1, contact_id: 42,
    message: { content: 'Hello, I need help!' }, status: 'open'
  })
});
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/conversations',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'inbox_id': 1, 'contact_id': 42,
          'message': {'content': 'Hello, I need help!'}, 'status': 'open'})
data = resp.json()

Messages

Messages are individual communications within a conversation. Send replies, notes, and attachments.

GET/api/v1/accounts/{account_id}/conversations/{conversation_id}/messages

Retrieve all messages for a specific conversation, ordered chronologically.

POST/api/v1/accounts/{account_id}/conversations/{conversation_id}/messages

Send a new message in a conversation. Supports text, private notes, and file attachments.

Body Parameters
contentstringrequiredThe message text content
message_typestringoutgoing (reply to customer) or activity (private note)
privatebooleanIf true, creates an internal note visible only to agents
content_attributesobjectAdditional message metadata
DELETE/api/v1/accounts/{account_id}/conversations/{conversation_id}/messages/{id}

Delete a specific message from a conversation. Cannot be undone.

Send Message
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/conversations/42/messages" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "content": "Thanks for reaching out!", "message_type": "outgoing" }'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/conversations/42/messages")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req.body = { content: 'Thanks for reaching out!', message_type: 'outgoing' }.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/conversations/42/messages', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({ content: 'Thanks for reaching out!', message_type: 'outgoing' })
});
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/conversations/42/messages',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'content': 'Thanks for reaching out!', 'message_type': 'outgoing'})

Contacts

Contacts represent individuals who interact with your team. Manage customer profiles, custom attributes, and conversation history.

GET/api/v1/accounts/{account_id}/contacts

Retrieve a paginated list of all contacts in your account.

Query Parameters
pageintegerPage number (default: 1, 15 per page)
sortstringSort field: name, email, phone_number, last_activity_at, created_at
order_bystringasc or desc
POST/api/v1/accounts/{account_id}/contacts

Create a new contact in your account.

Body Parameters
namestringContact's full name
emailstringContact's email address
phone_numberstringContact's phone number (E.164 format)
avatar_urlstringURL to the contact's avatar image
identifierstringUnique external identifier for the contact
lifecycle_stagestringContact lifecycle stage: lead, mql, sql, opportunity, customer, evangelist, other
sourcestringHow the contact was acquired: website, referral, organic, paid, social, email, event, partner, other
custom_attributesobjectKey-value pairs of custom attributes
GET/api/v1/accounts/{account_id}/contacts/{id}

Retrieve a single contact by ID, including all profile data, lifecycle stage, source, and custom attributes.

PATCH/api/v1/accounts/{account_id}/contacts/{id}

Update contact profile information, lifecycle stage, source, custom attributes, or avatar.

Body Parameters
namestringContact's full name
emailstringContact's email address
phone_numberstringContact's phone number (E.164 format)
lifecycle_stagestringContact lifecycle stage: lead, mql, sql, opportunity, customer, evangelist, other
sourcestringHow the contact was acquired
custom_attributesobjectKey-value pairs of custom attributes
DELETE/api/v1/accounts/{account_id}/contacts/{id}

Permanently delete a contact and all associated data. This action cannot be undone.

GET/api/v1/accounts/{account_id}/contacts/{id}/conversations

Retrieve all conversations associated with a specific contact.

GET/api/v1/accounts/{account_id}/search?q={query}

Search contacts by name, email, phone number, or identifier.

Query Parameters
qstringrequiredSearch query string
pageintegerPage number

Contact Activities

Track interactions and activities for a specific contact โ€” calls, emails, meetings, notes, and tasks.

GET/api/v1/accounts/{account_id}/contacts/{contact_id}/activities

List all activities for a contact, ordered by most recent.

POST/api/v1/accounts/{account_id}/contacts/{contact_id}/activities

Log a new activity for a contact.

Body Parameters
activity_typestringrequiredType: call, email, meeting, note, task
titlestringrequiredActivity title
descriptionstringDetailed description or notes
due_datestringISO 8601 due date (for tasks)
completed_atstringISO 8601 timestamp when completed (null if not completed)
user_idintegerAgent who performed the activity
metadataobjectAdditional metadata key-value pairs
PATCH/api/v1/accounts/{account_id}/contacts/{contact_id}/activities/{id}

Update an existing activity for a contact.

DELETE/api/v1/accounts/{account_id}/contacts/{contact_id}/activities/{id}

Delete a contact activity.

Create Contact
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/contacts" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Jane Smith",
    "email": "jane@example.com",
    "phone_number": "+1234567890",
    "custom_attributes": { "plan": "enterprise" }
  }'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/contacts")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req.body = {
  name: 'Jane Smith', email: 'jane@example.com',
  phone_number: '+1234567890',
  custom_attributes: { plan: 'enterprise' }
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/contacts', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'Jane Smith', email: 'jane@example.com',
    phone_number: '+1234567890',
    custom_attributes: { plan: 'enterprise' }
  })
});
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/contacts',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'name': 'Jane Smith', 'email': 'jane@example.com',
          'phone_number': '+1234567890',
          'custom_attributes': {'plan': 'enterprise'}})

Inboxes

Inboxes are communication channels (website chat, email, social media, API) where customer conversations originate.

GET/api/v1/accounts/{account_id}/inboxes

Returns all inboxes configured for the account.

GET/api/v1/accounts/{account_id}/inboxes/{id}

Retrieve details for a specific inbox including channel configuration.

POST/api/v1/accounts/{account_id}/inboxes

Create a new inbox. Required fields depend on the channel type (web, email, api, etc.).

Body Parameters
namestringrequiredInbox display name
channelobjectrequiredChannel configuration including type
List Inboxes
Request
curl "https://api.supportly.io/api/v1/accounts/1/inboxes" \
  -H "api_access_token: YOUR_TOKEN"
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/inboxes")
req = Net::HTTP::Get.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/inboxes', {
  headers: { 'api_access_token': 'YOUR_TOKEN' }
});
const data = await resp.json();
Request
import requests

resp = requests.get('https://api.supportly.io/api/v1/accounts/1/inboxes',
    headers={'api_access_token': 'YOUR_TOKEN'})
data = resp.json()

Agents

Agents are team members who handle conversations. Manage agent accounts, availability, and assignments.

GET/api/v1/accounts/{account_id}/agents

Returns all agents (team members) for the account.

POST/api/v1/accounts/{account_id}/agents

Invite a new agent to your account.

Body Parameters
namestringrequiredAgent's full name
emailstringrequiredAgent's email address
rolestringrequiredagent or administrator
PATCH/api/v1/accounts/{account_id}/agents/{id}

Update an agent's name, role, or availability status.

DELETE/api/v1/accounts/{account_id}/agents/{id}

Remove an agent from your account. Their conversations will be unassigned.

List Agents
Request
curl "https://api.supportly.io/api/v1/accounts/1/agents" \
  -H "api_access_token: YOUR_TOKEN"
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/agents")
req = Net::HTTP::Get.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/agents', {
  headers: { 'api_access_token': 'YOUR_TOKEN' }
});
const data = await resp.json();
Request
import requests

resp = requests.get('https://api.supportly.io/api/v1/accounts/1/agents',
    headers={'api_access_token': 'YOUR_TOKEN'})
data = resp.json()

Teams

Teams group agents together for organized assignment, routing, and management of conversations.

GET/api/v1/accounts/{account_id}/teams

Returns all teams in the account.

POST/api/v1/accounts/{account_id}/teams

Create a new team.

Body Parameters
namestringrequiredTeam name
descriptionstringTeam description
allow_auto_assignbooleanEnable auto-assignment for this team
Create Team
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/teams" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Sales Team",
    "description": "Handles all sales inquiries",
    "allow_auto_assign": true
  }'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/teams")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req.body = {
  name: 'Sales Team',
  description: 'Handles all sales inquiries',
  allow_auto_assign: true
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/teams', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'Sales Team',
    description: 'Handles all sales inquiries',
    allow_auto_assign: true
  })
});
const data = await resp.json();
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/teams',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'name': 'Sales Team',
          'description': 'Handles all sales inquiries',
          'allow_auto_assign': True})
data = resp.json()

Labels

Organize conversations and contacts with custom labels for categorization and filtering.

GET/api/v1/accounts/{account_id}/labels

Returns all labels configured for the account.

POST/api/v1/accounts/{account_id}/labels

Create a new label.

Body Parameters
titlestringrequiredLabel name (lowercase, no spaces)
descriptionstringLabel description
colorstringHex color code (e.g., #FF5733)
show_on_sidebarbooleanWhether to show in the sidebar
Create Label
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/labels" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "priority-customer",
    "description": "High-priority customer requests",
    "color": "#FF5733",
    "show_on_sidebar": true
  }'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/labels")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req.body = {
  title: 'priority-customer',
  description: 'High-priority customer requests',
  color: '#FF5733',
  show_on_sidebar: true
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/labels', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    title: 'priority-customer',
    description: 'High-priority customer requests',
    color: '#FF5733',
    show_on_sidebar: true
  })
});
const data = await resp.json();
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/labels',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'title': 'priority-customer',
          'description': 'High-priority customer requests',
          'color': '#FF5733',
          'show_on_sidebar': True})
data = resp.json()

Automation Rules

Automate repetitive tasks with rules that trigger actions based on conversation events and conditions.

GET/api/v1/accounts/{account_id}/automation_rules

Returns all automation rules for the account.

POST/api/v1/accounts/{account_id}/automation_rules

Create an automation rule with event triggers, conditions, and actions.

List Automation Rules
Request
curl "https://api.supportly.io/api/v1/accounts/1/automation_rules" \
  -H "api_access_token: YOUR_TOKEN"
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/automation_rules")
req = Net::HTTP::Get.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/automation_rules', {
  headers: { 'api_access_token': 'YOUR_TOKEN' }
});
const data = await resp.json();
Request
import requests

resp = requests.get('https://api.supportly.io/api/v1/accounts/1/automation_rules',
    headers={'api_access_token': 'YOUR_TOKEN'})
data = resp.json()

Canned Responses

Pre-written reply templates that agents can quickly insert into conversations.

GET/api/v1/accounts/{account_id}/canned_responses

Returns all canned responses. Optionally search by keyword.

POST/api/v1/accounts/{account_id}/canned_responses

Create a canned response template.

Body Parameters
short_codestringrequiredShortcut code for quick access
contentstringrequiredTemplate content
Create Canned Response
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/canned_responses" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "short_code": "greeting",
    "content": "Hello! Thanks for reaching out. How can I help you today?"
  }'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/canned_responses")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req.body = {
  short_code: 'greeting',
  content: 'Hello! Thanks for reaching out. How can I help you today?'
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/canned_responses', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    short_code: 'greeting',
    content: 'Hello! Thanks for reaching out. How can I help you today?'
  })
});
const data = await resp.json();
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/canned_responses',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'short_code': 'greeting',
          'content': 'Hello! Thanks for reaching out. How can I help you today?'})
data = resp.json()

Campaigns

Create and manage outbound campaigns to engage customers proactively.

GET/api/v1/accounts/{account_id}/campaigns

Returns all campaigns for the account.

POST/api/v1/accounts/{account_id}/campaigns

Create a new campaign.

Body Parameters
titlestringrequiredCampaign title
messagestringrequiredCampaign message content
inbox_idintegerrequiredInbox to send from
Create Campaign
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/campaigns" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Welcome Campaign",
    "message": "Hi there! Welcome to our platform. How can we assist you?",
    "inbox_id": 1
  }'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/campaigns")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req.body = {
  title: 'Welcome Campaign',
  message: 'Hi there! Welcome to our platform. How can we assist you?',
  inbox_id: 1
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/campaigns', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    title: 'Welcome Campaign',
    message: 'Hi there! Welcome to our platform. How can we assist you?',
    inbox_id: 1
  })
});
const data = await resp.json();
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/campaigns',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'title': 'Welcome Campaign',
          'message': 'Hi there! Welcome to our platform. How can we assist you?',
          'inbox_id': 1})
data = resp.json()

Webhooks

Receive real-time HTTP callbacks when events occur in your Supportly account.

Available Events: conversation_created, conversation_status_changed, conversation_updated, message_created, message_updated, contact_created, contact_updated, webwidget_triggered
GET/api/v1/accounts/{account_id}/webhooks

Returns all configured webhooks.

POST/api/v1/accounts/{account_id}/webhooks

Create a webhook subscription.

Body Parameters
urlstringrequiredThe URL to receive webhook payloads
subscriptionsarrayList of events to subscribe to
Webhook Payload
Event Payload
{
  "event": "message_created",
  "id": 42,
  "content": "Hello!",
  "message_type": "incoming",
  "conversation": {
    "id": 1,
    "status": "open"
  }
}

Captain AI

Leverage AI-powered features for intelligent assistance, knowledge management, and automated responses.

GET/api/v1/accounts/{account_id}/captain/assistants

Returns all AI assistants configured for the account.

GET/api/v1/accounts/{account_id}/captain/documents

Returns all knowledge base documents used by Captain AI.

POST/api/v1/accounts/{account_id}/captain/documents

Upload a document to the AI knowledge base.

Body Parameters
titlestringrequiredDocument title
contentstringrequiredDocument content
POST/api/v1/accounts/{account_id}/captain/copilot/threads

Create a new copilot conversation thread for agent assistance.

Upload Document
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/captain/documents" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Product FAQ",
    "content": "## Frequently Asked Questions\n\nQ: How do I reset my password?\nA: Go to Settings > Security > Reset Password."
  }'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/captain/documents")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req.body = {
  title: 'Product FAQ',
  content: '## Frequently Asked Questions\n\nQ: How do I reset my password?\nA: Go to Settings > Security > Reset Password.'
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/captain/documents', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    title: 'Product FAQ',
    content: '## Frequently Asked Questions...'
  })
});
const data = await resp.json();
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/captain/documents',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'title': 'Product FAQ',
          'content': '## Frequently Asked Questions...'})
data = resp.json()

AI Help Center

AI-powered tools for creating, rewriting, and managing help center articles. Generate articles from prompts, conversations, or rewrite existing content.

POST/api/v1/accounts/{account_id}/portals/{portal_id}/help_center/ai_articles/generate

Generate a new help center article using AI. Supports generation from a prompt, an existing conversation, or a topic suggestion.

Body Parameters
promptstringA text prompt describing the article to generate
conversation_idintegerGenerate an article based on a resolved conversation
category_idintegerCategory to assign the generated article to
POST/api/v1/accounts/{account_id}/portals/{portal_id}/help_center/ai_articles/rewrite

Rewrite an existing article using AI to improve clarity, tone, or completeness.

Body Parameters
article_idintegerrequiredThe article to rewrite
instructionsstringSpecific rewriting instructions (e.g., "make it shorter", "add examples")
GET/api/v1/accounts/{account_id}/portals/{portal_id}/help_center/ai_articles/suggest_topics

Get AI-suggested article topics based on recent conversations and knowledge gaps.

Generate Article
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/portals/1/help_center/ai_articles/generate" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "How to set up two-factor authentication",
    "category_id": 5
  }'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/portals/1/help_center/ai_articles/generate")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req.body = { prompt: 'How to set up two-factor authentication', category_id: 5 }.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/portals/1/help_center/ai_articles/generate', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({ prompt: 'How to set up two-factor authentication', category_id: 5 })
});
const data = await resp.json();
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/portals/1/help_center/ai_articles/generate',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'prompt': 'How to set up two-factor authentication', 'category_id': 5})
data = resp.json()

Spam Detection

AI-powered spam detection scores incoming messages for spam likelihood. Flag or unflag conversations as spam with administrative controls.

POST/api/v1/accounts/{account_id}/conversations/{conversation_id}/spam

Mark a conversation as spam. The conversation will be flagged and can be filtered in the admin view.

DELETE/api/v1/accounts/{account_id}/conversations/{conversation_id}/spam

Remove the spam flag from a conversation.

Automatic Detection: Supportly automatically scores incoming messages for spam likelihood. Conversations exceeding the spam threshold are flagged automatically. Use these endpoints for manual overrides.
Mark as Spam
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/conversations/42/spam" \
  -H "api_access_token: YOUR_TOKEN"
Request
uri = URI("https://api.supportly.io/api/v1/accounts/1/conversations/42/spam")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/conversations/42/spam', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN' }
});
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/conversations/42/spam',
    headers={'api_access_token': 'YOUR_TOKEN'})

Portals

Help center portals provide self-service knowledge bases for your customers.

GET/api/v1/accounts/{account_id}/portals

Returns all help center portals.

POST/api/v1/accounts/{account_id}/portals

Create a new help center portal.

Body Parameters
namestringrequiredPortal name
slugstringrequiredURL-friendly identifier
colorstringBrand color hex code
Create Portal
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/portals" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Help Center",
    "slug": "help-center",
    "color": "#1B83FF"
  }'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/portals")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req.body = {
  name: 'Help Center',
  slug: 'help-center',
  color: '#1B83FF'
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/portals', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'Help Center',
    slug: 'help-center',
    color: '#1B83FF'
  })
});
const data = await resp.json();
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/portals',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'name': 'Help Center',
          'slug': 'help-center',
          'color': '#1B83FF'})
data = resp.json()

Articles

Help center articles provide self-service support content for your customers.

GET/api/v1/accounts/{account_id}/portals/{portal_slug}/articles

Returns all articles for a portal. Supports filtering by category and status.

POST/api/v1/accounts/{account_id}/portals/{portal_slug}/articles

Create a new help center article.

Body Parameters
titlestringrequiredArticle title
contentstringrequiredArticle content (Markdown)
statusstringdraft or published
category_idintegerCategory ID to organize the article
PATCH/api/v1/accounts/{account_id}/portals/{portal_slug}/articles/{id}

Update an existing article's content, title, or status.

DELETE/api/v1/accounts/{account_id}/portals/{portal_slug}/articles/{id}

Delete an article from the help center.

Create Article
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/portals/my-portal/articles" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Getting Started with Supportly",
    "content": "## Welcome\nThis guide will help you...",
    "status": "published",
    "category_id": 1
  }'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/portals/my-portal/articles")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req.body = { title: 'Getting Started with Supportly', content: '## Welcome...', status: 'published', category_id: 1 }.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/portals/my-portal/articles', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    title: 'Getting Started with Supportly',
    content: '## Welcome...', status: 'published', category_id: 1
  })
});
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/portals/my-portal/articles',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'title': 'Getting Started with Supportly',
          'content': '## Welcome...', 'status': 'published', 'category_id': 1})

Categories

Organize help center articles into categories for better navigation.

GET/api/v1/accounts/{account_id}/portals/{portal_slug}/categories

Returns all categories for a portal.

POST/api/v1/accounts/{account_id}/portals/{portal_slug}/categories

Create a new category.

Body Parameters
namestringrequiredCategory name
descriptionstringCategory description
localestringLocale code (e.g., en)
Create Category
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/portals/my-portal/categories" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Getting Started",
    "description": "Introductory guides and tutorials",
    "locale": "en"
  }'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/portals/my-portal/categories")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req.body = {
  name: 'Getting Started',
  description: 'Introductory guides and tutorials',
  locale: 'en'
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/portals/my-portal/categories', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'Getting Started',
    description: 'Introductory guides and tutorials',
    locale: 'en'
  })
});
const data = await resp.json();
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/portals/my-portal/categories',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'name': 'Getting Started',
          'description': 'Introductory guides and tutorials',
          'locale': 'en'})
data = resp.json()

Client Portal

The Client Portal provides a branded self-service interface where end-users can sign in via magic link, view their support tickets, reply to existing tickets, and create new ones.

Public API: Client Portal endpoints are public and do not require an api_access_token. Authentication is handled via magic link tokens. The base path is /hc/{portal_slug}/client-portal/api.
GET/hc/{slug}/client-portal/api/

Returns portal information including name, branding, and configuration for the client portal login page.

POST/hc/{slug}/client-portal/api/request-magic-link

Send a magic sign-in link to the customer's email address. The link expires after a set period.

Body Parameters
emailstringrequiredThe customer's email address
POST/hc/{slug}/client-portal/api/verify-magic-link

Verify a magic link token and return an authenticated session. Returns the customer's contact record and a session token.

Body Parameters
tokenstringrequiredThe magic link token from the email
GET/hc/{slug}/client-portal/api/tickets

List all tickets for the authenticated customer. Requires the session token from magic link verification.

Headers
X-Portal-TokenstringrequiredSession token from magic link verification
POST/hc/{slug}/client-portal/api/tickets

Create a new support ticket from the client portal.

Body Parameters
subjectstringrequiredTicket subject line
descriptionstringrequiredTicket description / initial message
inbox_idintegerTarget inbox (uses default if not specified)
GET/hc/{slug}/client-portal/api/tickets/{ticket_id}

Get details of a specific ticket including all messages.

POST/hc/{slug}/client-portal/api/tickets/{ticket_id}/messages

Reply to an existing ticket with a new message.

Body Parameters
contentstringrequiredThe reply message content
Request Magic Link
Request
curl -X POST "https://api.supportly.io/hc/my-portal/client-portal/api/request-magic-link" \
  -H "Content-Type: application/json" \
  -d '{"email": "customer@example.com"}'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/hc/my-portal/client-portal/api/request-magic-link")
req = Net::HTTP::Post.new(uri)
req['Content-Type'] = 'application/json'
req.body = { email: 'customer@example.com' }.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
Request
const resp = await fetch('https://api.supportly.io/hc/my-portal/client-portal/api/request-magic-link', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email: 'customer@example.com' })
});
const data = await resp.json();
Request
import requests

resp = requests.post('https://api.supportly.io/hc/my-portal/client-portal/api/request-magic-link',
    json={'email': 'customer@example.com'})
data = resp.json()
Ticket List Response
Response
{
  "tickets": [
    {
      "id": 1,
      "subject": "Cannot reset password",
      "status": "open",
      "created_at": "2024-12-01T10:30:00Z",
      "last_message_at": "2024-12-02T14:22:00Z",
      "message_count": 4
    }
  ]
}

Supportly Integrations

Connect third-party CRMs, scheduling, and commerce platforms via OAuth. Supported providers: HubSpot, Salesforce, Pipedrive, Calendly, Schedly, Stripe, Zendesk, Jira, Slack, and Shopify.

GET/api/v1/accounts/{account_id}/supportly_integrations

List all available Supportly integrations and their connection status.

GET/api/v1/accounts/{account_id}/supportly_integrations/{provider}

Get details for a specific integration provider, including connection status and configuration.

Path Parameters
providerstringrequiredProvider name: hubspot, salesforce, pipedrive, calendly, schedly, stripe, zendesk, jira, slack, shopify
POST/api/v1/accounts/{account_id}/supportly_integrations/{provider}/connect

Initiate an OAuth connection to a provider. Returns an authorization URL to redirect the user to.

POST/api/v1/accounts/{account_id}/supportly_integrations/{provider}/callback

Complete the OAuth flow by exchanging the authorization code for access tokens.

Body Parameters
codestringrequiredOAuth authorization code
code_verifierstringPKCE code verifier (required for PKCE providers)
DELETE/api/v1/accounts/{account_id}/supportly_integrations/{provider}/disconnect

Disconnect an integration and revoke stored tokens.

GET/api/v1/accounts/{account_id}/supportly_integrations/{provider}/contacts/{contact_id}

Fetch CRM data for a contact from the connected provider. Returns matching records, deals, activities, and other provider-specific data.

POST/api/v1/accounts/{account_id}/supportly_integrations/{provider}/contacts/{contact_id}/sync

Sync a Supportly contact to the connected CRM. Creates or updates the contact record in the provider.

POST/api/v1/accounts/{account_id}/supportly_integrations/{provider}/contacts/{contact_id}/activity

Create an activity note on the contact's CRM record (e.g., log a call, note, or task).

Body Parameters
contentstringrequiredActivity content / note text
activity_typestringType of activity: note, call, task
POST/api/v1/accounts/{account_id}/supportly_integrations/{provider}/action

Execute a provider-specific action. Actions vary by provider (e.g., fetch bookings for Schedly, get event types for Calendly, fetch deals for HubSpot).

Body Parameters
actionstringrequiredThe action to perform (provider-specific)
paramsobjectAction parameters (varies by action)
Schedly Actions: get_bookings, get_available_slots, create_booking, cancel_booking, reschedule_booking, get_teams, get_event_types, route_lead
Calendly Actions: get_event_types, get_scheduled_events
HubSpot/Salesforce/Pipedrive Actions: get_deals, get_companies, search_contacts
PUT/api/v1/accounts/{account_id}/supportly_integrations/{provider}/settings

Update integration-specific settings (e.g., enable Captain AI scheduling for Schedly, set default event type).

Body Parameters
settingsobjectrequiredProvider settings object. Keys vary by provider.
Schedly Settings: captain_scheduling_enabled (boolean), default_event_type_id (string), default_routing_config_id (string)
POST/api/v1/accounts/{account_id}/supportly_integrations/{provider}/knowledge/toggle

Enable or disable AI knowledge sync for a provider. When enabled, data from the provider is synced into Captain AI's knowledge base.

POST/api/v1/accounts/{account_id}/supportly_integrations/{provider}/knowledge/sync

Trigger an immediate knowledge sync from the provider.

GET/api/v1/accounts/{account_id}/supportly_integrations/knowledge/status

Get the knowledge sync status across all connected providers.

Connect Provider
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/supportly_integrations/schedly/connect" \
  -H "api_access_token: YOUR_TOKEN"
Request
uri = URI("https://api.supportly.io/api/v1/accounts/1/supportly_integrations/schedly/connect")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/supportly_integrations/schedly/connect', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN' }
});
const data = await resp.json();
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/supportly_integrations/schedly/connect',
    headers={'api_access_token': 'YOUR_TOKEN'})
data = resp.json()
Connect Response
Response
{
  "authorization_url": "https://app.schedly.io/oauth/authorize?client_id=...&scope=read_bookings+write_bookings+read_profile+read_event_types+read_availability+read_teams+write_webhooks"
}
Provider Action (Schedly)
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/supportly_integrations/schedly/action" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "get_available_slots",
    "params": {
      "event_type_id": "evt_123",
      "start_time": "2025-03-10T00:00:00Z",
      "end_time": "2025-03-14T23:59:59Z",
      "timezone": "America/New_York"
    }
  }'
Request
uri = URI("https://api.supportly.io/api/v1/accounts/1/supportly_integrations/schedly/action")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req.body = {
  action: 'get_available_slots',
  params: { event_type_id: 'evt_123', start_time: '2025-03-10T00:00:00Z',
            end_time: '2025-03-14T23:59:59Z', timezone: 'America/New_York' }
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/supportly_integrations/schedly/action', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    action: 'get_available_slots',
    params: { event_type_id: 'evt_123', start_time: '2025-03-10T00:00:00Z',
              end_time: '2025-03-14T23:59:59Z', timezone: 'America/New_York' }
  })
});
const data = await resp.json();
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/supportly_integrations/schedly/action',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'action': 'get_available_slots',
          'params': {'event_type_id': 'evt_123', 'start_time': '2025-03-10T00:00:00Z',
                     'end_time': '2025-03-14T23:59:59Z', 'timezone': 'America/New_York'}})
data = resp.json()

Companies

Companies represent organizations that your contacts belong to. Manage company profiles with industry, revenue, employee data, and social links for account-based support.

GET/api/v1/accounts/{account_id}/companies

Returns a paginated list of all companies in your account.

Query Parameters
pageintegerPage number (default: 1, 25 per page)
sortstringSort field: name, domain, created_at
GET/api/v1/accounts/{account_id}/companies/search?q={query}

Search companies by name or domain.

Query Parameters
qstringrequiredSearch query string
GET/api/v1/accounts/{account_id}/companies/{id}

Retrieve a single company with all profile data, linked contacts, and deals.

POST/api/v1/accounts/{account_id}/companies

Create a new company.

Body Parameters
namestringrequiredCompany name
domainstringCompany website domain
descriptionstringCompany description
industrystringIndustry vertical (e.g. Technology, Healthcare, Finance)
employee_countintegerNumber of employees
annual_revenuedecimalAnnual revenue in USD
phonestringCompany phone number
addressstringFull mailing address
social_linksobjectSocial profiles: twitter, facebook, linkedin, github
PATCH/api/v1/accounts/{account_id}/companies/{id}

Update company information. Accepts all the same body parameters as create.

DELETE/api/v1/accounts/{account_id}/companies/{id}

Delete a company. Contacts will be unlinked but not deleted.

Company Deals

Manage deals associated with a specific company.

GET/api/v1/accounts/{account_id}/companies/{company_id}/deals

List all deals for a company.

POST/api/v1/accounts/{account_id}/companies/{company_id}/deals

Create a new deal under this company.

PATCH/api/v1/accounts/{account_id}/companies/{company_id}/deals/{id}

Update a company deal.

DELETE/api/v1/accounts/{account_id}/companies/{company_id}/deals/{id}

Delete a company deal.

Company Activities

Track interactions and activities for a specific company.

GET/api/v1/accounts/{account_id}/companies/{company_id}/activities

List all activities for a company.

POST/api/v1/accounts/{account_id}/companies/{company_id}/activities

Log a new activity for a company.

Body Parameters
activity_typestringrequiredType: call, email, meeting, note, task
titlestringrequiredActivity title
descriptionstringDetailed description or notes
due_datestringISO 8601 due date
completed_atstringISO 8601 timestamp when completed
user_idintegerAgent who performed the activity
metadataobjectAdditional metadata
PATCH/api/v1/accounts/{account_id}/companies/{company_id}/activities/{id}

Update a company activity.

DELETE/api/v1/accounts/{account_id}/companies/{company_id}/activities/{id}

Delete a company activity.

Create Company
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/companies" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Inc",
    "domain": "acme.com",
    "description": "Leading technology company",
    "industry": "Technology",
    "employee_count": 150,
    "annual_revenue": 5000000,
    "phone": "+1-555-0100",
    "address": "123 Main St, San Francisco, CA 94102",
    "social_links": {
      "twitter": "https://twitter.com/acmeinc",
      "linkedin": "https://linkedin.com/company/acme"
    }
  }'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/companies")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req.body = {
  name: 'Acme Inc', domain: 'acme.com',
  industry: 'Technology', employee_count: 150,
  annual_revenue: 5000000, phone: '+1-555-0100',
  address: '123 Main St, San Francisco, CA 94102',
  social_links: { twitter: 'https://twitter.com/acmeinc', linkedin: 'https://linkedin.com/company/acme' }
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/companies', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'Acme Inc', domain: 'acme.com',
    industry: 'Technology', employee_count: 150,
    annual_revenue: 5000000, phone: '+1-555-0100',
    social_links: { twitter: 'https://twitter.com/acmeinc' }
  })
});
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/companies',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'name': 'Acme Inc', 'domain': 'acme.com',
          'industry': 'Technology', 'employee_count': 150,
          'annual_revenue': 5000000, 'phone': '+1-555-0100',
          'social_links': {'linkedin': 'https://linkedin.com/company/acme'}})
Response
JSON
{
  "id": 42,
  "name": "Acme Inc",
  "domain": "acme.com",
  "description": "Leading technology company",
  "industry": "Technology",
  "employee_count": 150,
  "annual_revenue": "5000000.0",
  "phone": "+1-555-0100",
  "address": "123 Main St, San Francisco, CA 94102",
  "social_links": {
    "twitter": "https://twitter.com/acmeinc",
    "linkedin": "https://linkedin.com/company/acme"
  },
  "contacts_count": 0,
  "avatar_url": null,
  "created_at": "2026-03-07T10:00:00.000Z",
  "updated_at": "2026-03-07T10:00:00.000Z"
}

Deals

Deals represent sales opportunities in your pipeline. Track deal stages, values, expected close dates, and link deals to contacts and companies.

GET/api/v1/accounts/{account_id}/contacts/{contact_id}/deals

List all deals for a specific contact.

POST/api/v1/accounts/{account_id}/contacts/{contact_id}/deals

Create a new deal linked to a contact.

Body Parameters
namestringrequiredDeal name or title
valuedecimalDeal value in your account currency
stagestringPipeline stage: new_deal, discovery, proposal, negotiation, won, lost
pipelinestringPipeline name (e.g. "default", "enterprise")
expected_close_datestringISO 8601 expected close date
company_idintegerAssociated company ID
assignee_idintegerAgent assigned to the deal
notesstringAdditional notes about the deal
PATCH/api/v1/accounts/{account_id}/contacts/{contact_id}/deals/{id}

Update a deal linked to a contact. Accepts all the same body parameters as create.

DELETE/api/v1/accounts/{account_id}/contacts/{contact_id}/deals/{id}

Delete a deal from a contact.

Company-Scoped Deals

The same CRUD operations are available scoped to a company.

GET/api/v1/accounts/{account_id}/companies/{company_id}/deals

List all deals for a company.

POST/api/v1/accounts/{account_id}/companies/{company_id}/deals

Create a deal under a company.

PATCH/api/v1/accounts/{account_id}/companies/{company_id}/deals/{id}

Update a company deal.

DELETE/api/v1/accounts/{account_id}/companies/{company_id}/deals/{id}

Delete a company deal.

Global Deal Update

PATCH/api/v1/accounts/{account_id}/deals/{id}

Update any deal by ID regardless of parent context. Useful for Kanban drag-drop stage changes.

Create Deal
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/contacts/5/deals" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Enterprise License Q2",
    "value": 25000,
    "stage": "proposal",
    "pipeline": "default",
    "expected_close_date": "2026-06-30",
    "assignee_id": 3
  }'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/contacts/5/deals")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req.body = {
  name: 'Enterprise License Q2',
  value: 25000, stage: 'proposal',
  pipeline: 'default',
  expected_close_date: '2026-06-30'
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/contacts/5/deals', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'Enterprise License Q2',
    value: 25000, stage: 'proposal',
    expected_close_date: '2026-06-30'
  })
});
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/contacts/5/deals',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'name': 'Enterprise License Q2',
          'value': 25000, 'stage': 'proposal',
          'expected_close_date': '2026-06-30'})
Response
JSON
{
  "id": 1,
  "name": "Enterprise License Q2",
  "value": "25000.0",
  "stage": "proposal",
  "pipeline": "default",
  "expected_close_date": "2026-06-30",
  "contact_id": 5,
  "company_id": null,
  "assignee_id": 3,
  "notes": null,
  "created_at": "2026-03-07T10:00:00.000Z",
  "updated_at": "2026-03-07T10:00:00.000Z"
}

Deal Tasks

Tasks associated with deals โ€” follow-ups, demos, contract reviews, and other action items to move deals forward.

GET/api/v1/accounts/{account_id}/deal_tasks?deal_id={deal_id}

List all tasks for a deal.

Query Parameters
deal_idintegerrequiredFilter tasks by deal
POST/api/v1/accounts/{account_id}/deal_tasks

Create a new task for a deal.

Body Parameters
deal_idintegerrequiredThe deal this task belongs to
titlestringrequiredTask title
descriptionstringTask description
due_datestringISO 8601 due date
completedbooleanWhether the task is completed (default: false)
assignee_idintegerAgent assigned to this task
PATCH/api/v1/accounts/{account_id}/deal_tasks/{id}

Update a deal task. Accepts all the same body parameters as create (except deal_id).

DELETE/api/v1/accounts/{account_id}/deal_tasks/{id}

Delete a deal task.

Create Deal Task
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/deal_tasks" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "deal_id": 1,
    "title": "Send proposal document",
    "description": "Attach pricing PDF and SOW",
    "due_date": "2026-03-15",
    "assignee_id": 3
  }'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/deal_tasks")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req.body = {
  deal_id: 1, title: 'Send proposal document',
  description: 'Attach pricing PDF and SOW',
  due_date: '2026-03-15'
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/deal_tasks', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    deal_id: 1, title: 'Send proposal document',
    due_date: '2026-03-15'
  })
});
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/deal_tasks',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'deal_id': 1, 'title': 'Send proposal document',
          'due_date': '2026-03-15'})

Deal Events

Deal events provide an audit trail of everything that happened on a deal โ€” stage changes, amount updates, assignments, task completions, and notes.

GET/api/v1/accounts/{account_id}/deal_events?deal_id={deal_id}

Retrieve the timeline of events for a deal, ordered by most recent first.

Query Parameters
deal_idintegerrequiredFilter events by deal
Deal events are generated automatically when deals are created, updated, or when tasks are completed. You cannot create events manually via the API.
List Deal Events
Request
curl "https://api.supportly.io/api/v1/accounts/1/deal_events?deal_id=1" \
  -H "api_access_token: YOUR_TOKEN"
Request
require 'net/http'

uri = URI("https://api.supportly.io/api/v1/accounts/1/deal_events?deal_id=1")
req = Net::HTTP::Get.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/deal_events?deal_id=1', {
  headers: { 'api_access_token': 'YOUR_TOKEN' }
});
Request
import requests

resp = requests.get('https://api.supportly.io/api/v1/accounts/1/deal_events',
    headers={'api_access_token': 'YOUR_TOKEN'},
    params={'deal_id': 1})
Response
JSON
[
  {
    "id": 3,
    "event_type": "stage_changed",
    "data": {
      "from": "discovery",
      "to": "proposal"
    },
    "performed_by": { "id": 3, "name": "Agent Smith" },
    "created_at": "2026-03-07T10:30:00.000Z"
  }
]

Deal Automations

Automate deal workflows โ€” trigger actions when deals reach specific stages, amounts, or conditions. Requires admin access.

GET/api/v1/accounts/{account_id}/deal_automations

List all deal automation rules.

GET/api/v1/accounts/{account_id}/deal_automations/{id}

Retrieve a single deal automation by ID.

POST/api/v1/accounts/{account_id}/deal_automations

Create a new deal automation rule.

Body Parameters
namestringrequiredAutomation rule name
descriptionstringRule description
trigger_typestringrequiredEvent that triggers the rule: stage_changed, deal_created, deal_closed_won, deal_closed_lost, deal_updated
action_typestringrequiredAction to perform: webhook, send_notification, assign_agent, create_activity, update_field
trigger_configobjectConfiguration for the trigger (e.g. {"from_stage": "discovery", "to_stage": "proposal"})
action_configobjectConfiguration for the action (e.g. {"agent_id": 3} for assign_agent)
enabledbooleanWhether the rule is active (default: true)
PATCH/api/v1/accounts/{account_id}/deal_automations/{id}

Update a deal automation rule.

DELETE/api/v1/accounts/{account_id}/deal_automations/{id}

Delete a deal automation rule.

Create Deal Automation
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/deal_automations" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Auto-assign large deals",
    "trigger_type": "deal_created",
    "action_type": "assign_agent",
    "trigger_config": {"min_value": 10000},
    "action_config": {"agent_id": 3},
    "enabled": true
  }'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/deal_automations")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req.body = {
  name: 'Auto-assign large deals',
  trigger_type: 'deal_created',
  action_type: 'assign_agent',
  trigger_config: { min_value: 10000 },
  action_config: { agent_id: 3 }
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/deal_automations', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'Auto-assign large deals',
    trigger_type: 'deal_created',
    action_type: 'assign_agent',
    trigger_config: { min_value: 10000 },
    action_config: { agent_id: 3 }
  })
});
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/deal_automations',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'name': 'Auto-assign large deals',
          'trigger_type': 'deal_created',
          'action_type': 'assign_agent',
          'trigger_config': {'min_value': 10000},
          'action_config': {'agent_id': 3}})

Activities

Activities are a unified CRM interaction log. Track calls, emails, meetings, notes, and tasks across contacts and companies. Activities are always scoped to either a contact or a company โ€” see the Contacts and Companies sections for the full endpoint reference.

Activity endpoints are nested under contacts and companies:
GET/POST /contacts/{contact_id}/activities
PATCH/DELETE /contacts/{contact_id}/activities/{id}
GET/POST /companies/{company_id}/activities
PATCH/DELETE /companies/{company_id}/activities/{id}
Activity Object Fields
idintegerUnique activity identifier
activity_typestringcall, email, meeting, note, task
titlestringActivity title
descriptionstringDetailed description
due_datestringISO 8601 due date (for tasks)
completed_atstringISO 8601 timestamp when completed
user_idintegerAgent who performed the activity
metadataobjectAdditional metadata key-value pairs
created_atstringISO 8601 creation timestamp
updated_atstringISO 8601 last update timestamp
Create Contact Activity
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/contacts/5/activities" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "activity_type": "call",
    "title": "Discovery call",
    "description": "Discussed requirements and timeline"
  }'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/contacts/5/activities")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req.body = {
  activity_type: 'call',
  title: 'Discovery call',
  description: 'Discussed requirements and timeline'
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/contacts/5/activities', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    activity_type: 'call',
    title: 'Discovery call',
    description: 'Discussed requirements and timeline'
  })
});
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/contacts/5/activities',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'activity_type': 'call',
          'title': 'Discovery call',
          'description': 'Discussed requirements and timeline'})
Response
JSON
{
  "id": 1,
  "activity_type": "call",
  "title": "Discovery call",
  "description": "Discussed requirements and timeline",
  "due_date": null,
  "completed_at": null,
  "user_id": null,
  "metadata": {},
  "created_at": "2026-03-07T10:00:00.000Z",
  "updated_at": "2026-03-07T10:00:00.000Z"
}

Integrations

Connect third-party services like Slack, Dialogflow, and OpenAI to extend Supportly's capabilities.

GET/api/v1/accounts/{account_id}/integrations/apps

Returns all available integration apps and their connection status.

POST/api/v1/accounts/{account_id}/integrations/hooks

Create a new integration connection (hook).

DELETE/api/v1/accounts/{account_id}/integrations/hooks/{id}

Disconnect an integration.

Integration Connect Response
Response
{
  "authorization_url": "https://provider.com/oauth/authorize?client_id=...",
  "code_verifier": "abc123..."  // Only for PKCE providers
}

Form Capture

Automatically intercept website form submissions to create contacts and conversations. Includes form discovery, smart field mapping, and automatic contact/company creation.

GET/api/v1/accounts/{account_id}/inboxes/{inbox_id}/form_capture

Get the form capture configuration for a web widget inbox, including tracked forms, field mappings, and label suggestions.

PATCH/api/v1/accounts/{account_id}/inboxes/{inbox_id}/form_capture

Update form capture settings โ€” enable/disable tracking, configure field mappings, and set auto-labeling rules.

Body Parameters
enabledbooleanEnable or disable form capture for this inbox
tracked_formsarrayList of form selectors to capture (CSS selectors)
field_mappingsobjectMap form fields to contact attributes
auto_labelsarrayLabels to automatically apply to captured conversations
POST/api/v1/widget/form_capture

Submit a captured form from the widget SDK. Creates a contact and conversation from the form data.

Body Parameters
website_tokenstringrequiredThe inbox website token
form_dataobjectrequiredCaptured form field values
form_urlstringThe URL where the form was submitted
form_selectorstringCSS selector of the captured form
This is a public widget endpoint. Authentication is via website_token, not api_access_token.
Configure Form Capture
Request
curl -X PATCH "https://api.supportly.io/api/v1/accounts/1/inboxes/5/form_capture" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true,
    "tracked_forms": ["#contact-form", ".demo-request"],
    "field_mappings": {
      "email": "email",
      "full_name": "name",
      "company": "company_name"
    },
    "auto_labels": ["form-lead", "website"]
  }'
Request
uri = URI("https://api.supportly.io/api/v1/accounts/1/inboxes/5/form_capture")
req = Net::HTTP::Patch.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req.body = { enabled: true, tracked_forms: ['#contact-form'],
             field_mappings: { email: 'email', full_name: 'name' } }.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/inboxes/5/form_capture', {
  method: 'PATCH',
  headers: { 'api_access_token': 'YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    enabled: true, tracked_forms: ['#contact-form'],
    field_mappings: { email: 'email', full_name: 'name' }
  })
});
Request
import requests

resp = requests.patch('https://api.supportly.io/api/v1/accounts/1/inboxes/5/form_capture',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'enabled': True, 'tracked_forms': ['#contact-form'],
          'field_mappings': {'email': 'email', 'full_name': 'name'}})

Site Explorer

Crawl public and authenticated websites to automatically learn product knowledge. Uses AI for content extraction, knowledge ingestion, and Auto-Intelligence analysis.

GET/api/v1/accounts/{account_id}/site_explorer_sessions

List all site explorer crawl sessions for the account.

POST/api/v1/accounts/{account_id}/site_explorer_sessions

Create a new site crawl session.

Body Parameters
urlstringrequiredThe starting URL to crawl
namestringFriendly name for the session
max_pagesintegerMaximum number of pages to crawl (default: 50)
include_patternsarrayURL patterns to include (glob syntax)
exclude_patternsarrayURL patterns to exclude (glob syntax)
POST/api/v1/accounts/{account_id}/site_explorer_sessions/{id}/start

Start or resume a crawl session.

POST/api/v1/accounts/{account_id}/site_explorer_sessions/{id}/pause

Pause an active crawl session.

POST/api/v1/accounts/{account_id}/site_explorer_sessions/{id}/resume

Resume a paused crawl session.

POST/api/v1/accounts/{account_id}/site_explorer_sessions/{id}/recrawl

Re-crawl all pages in the session to refresh knowledge.

GET/api/v1/accounts/{account_id}/site_explorer_sessions/{id}/pages

List all discovered and crawled pages within a session, including their status and extracted content.

GET/api/v1/accounts/{account_id}/site_explorer_sessions/{id}/knowledge

Get the AI-extracted knowledge documents generated from the crawl.

POST/api/v1/accounts/{account_id}/site_explorer_sessions/{id}/save_auth

Save authentication credentials for crawling pages behind a login. Supports cookie-based and form-based authentication.

Body Parameters
auth_typestringrequiredcookie or form
credentialsobjectrequiredAuthentication credentials (varies by auth_type)
POST/api/v1/accounts/{account_id}/site_explorer_sessions/{id}/auto_login

Attempt automatic login to the target website using saved credentials. Returns a session with authenticated cookies for crawling protected pages.

GET/api/v1/accounts/{account_id}/site_explorer_sessions/{id}/test_connection

Test whether the target URL is accessible and check for any authentication or connectivity issues.

GET/api/v1/accounts/{account_id}/site_explorer_sessions/{id}/proxy

Open an interactive visual proxy for the crawl session. Renders the target website through a proxy layer, allowing you to navigate and capture pages visually.

DELETE/api/v1/accounts/{account_id}/site_explorer_sessions/{id}

Delete a crawl session and all associated data.

Create Crawl Session
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/site_explorer_sessions" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://docs.example.com",
    "name": "Product Docs",
    "max_pages": 100,
    "include_patterns": ["/docs/*", "/help/*"],
    "exclude_patterns": ["/blog/*"]
  }'
Request
uri = URI("https://api.supportly.io/api/v1/accounts/1/site_explorer_sessions")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req.body = { url: 'https://docs.example.com', name: 'Product Docs',
             max_pages: 100 }.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/site_explorer_sessions', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    url: 'https://docs.example.com', name: 'Product Docs', max_pages: 100
  })
});
const data = await resp.json();
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/site_explorer_sessions',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'url': 'https://docs.example.com', 'name': 'Product Docs', 'max_pages': 100})
data = resp.json()
Session Response
Response
{
  "id": 12,
  "url": "https://docs.example.com",
  "name": "Product Docs",
  "status": "pending",
  "pages_discovered": 0,
  "pages_crawled": 0,
  "max_pages": 100,
  "created_at": "2025-03-01T10:00:00Z"
}

Billing & Subscriptions

Manage subscriptions, payment methods, invoices, and add-ons. Supportly uses a three-tier model: Free, Pro, and Enterprise, with optional add-ons for AI Credits and Remove Branding.

GET/api/v1/accounts/{account_id}/supportly_billing

Get the current billing status, including active plan, subscription details, usage, and add-ons.

POST/api/v1/accounts/{account_id}/supportly_billing/checkout

Create a Stripe Checkout session to subscribe to a plan or change plans.

Body Parameters
price_idstringrequiredStripe price ID for the target plan
billing_periodstringmonthly or yearly (default: monthly)
POST/api/v1/accounts/{account_id}/supportly_billing/toggle_ai_overage

Enable or disable AI overage billing ($0.06/credit). When enabled, AI continues working beyond plan limits with metered usage billing.

POST/api/v1/accounts/{account_id}/supportly_billing/branding_addon

Subscribe to the "Remove Supportly Branding" add-on ($19/month). Removes all Supportly branding from the widget and help center.

POST/api/v1/accounts/{account_id}/supportly_billing/cancel_branding_addon

Cancel the branding removal add-on. Branding will be restored at the end of the current billing period.

POST/api/v1/accounts/{account_id}/supportly_billing/cancel

Cancel the current subscription. The account will be downgraded to the Free plan at the end of the billing period.

GET/api/v1/accounts/{account_id}/supportly_billing/invoices

List all billing invoices for the account.

GET/api/v1/accounts/{account_id}/supportly_billing/payment_methods

List all payment methods on file for the account.

POST/api/v1/accounts/{account_id}/supportly_billing/setup_intent

Create a Stripe SetupIntent for adding a new payment method without an immediate charge.

POST/api/v1/accounts/{account_id}/supportly_billing/set_default_payment_method

Set a payment method as the default for future charges.

Body Parameters
payment_method_idstringrequiredStripe payment method ID
DELETE/api/v1/accounts/{account_id}/supportly_billing/remove_payment_method

Remove a payment method from the account.

Body Parameters
payment_method_idstringrequiredStripe payment method ID to remove
GET/api/v1/accounts/{account_id}/supportly_billing/stripe_config

Get Stripe publishable key and configuration for client-side Stripe.js integration.

Billing Status
Request
curl "https://api.supportly.io/api/v1/accounts/1/supportly_billing" \
  -H "api_access_token: YOUR_TOKEN"
Request
uri = URI("https://api.supportly.io/api/v1/accounts/1/supportly_billing")
req = Net::HTTP::Get.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/supportly_billing', {
  headers: { 'api_access_token': 'YOUR_TOKEN' }
});
const data = await resp.json();
Request
import requests

resp = requests.get('https://api.supportly.io/api/v1/accounts/1/supportly_billing',
    headers={'api_access_token': 'YOUR_TOKEN'})
data = resp.json()
Billing Response
Response
{
  "plan": "pro",
  "status": "active",
  "billing_period": "monthly",
  "current_period_end": "2025-04-01T00:00:00Z",
  "agent_count": 5,
  "agent_limit": 10,
  "addons": {
    "ai_credits": { "active": true, "remaining": 850 },
    "remove_branding": { "active": false }
  },
  "stripe_customer_id": "cus_..."
}

Data Migration

Import data from other platforms like Intercom, Zendesk, and Freshdesk into Supportly.

POST/api/v1/accounts/{account_id}/data_migrations

Start a data migration from a supported platform.

Body Parameters
sourcestringrequiredSource platform: intercom, zendesk, freshdesk
credentialsobjectrequiredAuthentication credentials for the source platform
GET/api/v1/accounts/{account_id}/data_migrations/{id}

Check the status of a data migration.

Migration Status
Response
{
  "id": 1,
  "source": "intercom",
  "status": "in_progress",
  "progress": 65,
  "records_imported": 1250,
  "total_records": 1920
}

Developer OAuth Apps

Create and manage OAuth 2.0 applications for third-party integrations with Supportly.

GET/api/v1/accounts/{account_id}/developer_apps

Returns all OAuth applications created by your account.

POST/api/v1/accounts/{account_id}/developer_apps

Register a new OAuth application.

Body Parameters
namestringrequiredApplication name
redirect_urisarrayrequiredArray of allowed redirect URIs
scopesarrayRequested OAuth scopes
GET/oauth/token/info

Introspect the current OAuth token to retrieve scope, expiry, and ownership details.

Developer App Response
Create Response
{
  "id": 1,
  "name": "My Integration",
  "client_id": "abc123...",
  "client_secret": "secret_shown_only_once...",
  "redirect_uris": ["https://yourapp.com/callback"],
  "scopes": ["conversations.read", "contacts.read"]
}
Token Introspection
GET /oauth/token/info Response
{
  "resource_owner_id": 42,
  "scope": "conversations.read contacts.read",
  "expires_in": 82400,
  "application": { "uid": "abc123..." },
  "created_at": 1708000000
}

Account

Manage your Supportly account settings, including name, locale, and auto-resolve configuration.

GET/api/v1/accounts/{account_id}

Retrieve account details including settings and feature flags.

PATCH/api/v1/accounts/{account_id}

Update account settings.

Body Parameters
namestringAccount name
localestringDefault locale
auto_resolve_durationintegerAuto-resolve conversations after N days
Get Account Info
Request
curl "https://api.supportly.io/api/v1/accounts/1" \
  -H "api_access_token: YOUR_TOKEN"
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1")
req = Net::HTTP::Get.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1', {
  headers: { 'api_access_token': 'YOUR_TOKEN' }
});
const data = await resp.json();
Request
import requests

resp = requests.get('https://api.supportly.io/api/v1/accounts/1',
    headers={'api_access_token': 'YOUR_TOKEN'})
data = resp.json()

Custom Attributes

Define custom data fields for conversations and contacts to capture business-specific information.

GET/api/v1/accounts/{account_id}/custom_attribute_definitions

Returns all custom attribute definitions.

POST/api/v1/accounts/{account_id}/custom_attribute_definitions

Define a new custom attribute.

Body Parameters
attribute_display_namestringrequiredDisplay name for the attribute
attribute_display_typestringrequiredType: text, number, link, date, list, checkbox
attribute_modelstringrequiredconversation_attribute or contact_attribute
Create Custom Attribute
Request
curl -X POST "https://api.supportly.io/api/v1/accounts/1/custom_attribute_definitions" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "attribute_display_name": "Subscription Plan",
    "attribute_display_type": "list",
    "attribute_model": "contact_attribute"
  }'
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v1/accounts/1/custom_attribute_definitions")
req = Net::HTTP::Post.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req.body = {
  attribute_display_name: 'Subscription Plan',
  attribute_display_type: 'list',
  attribute_model: 'contact_attribute'
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Request
const resp = await fetch('https://api.supportly.io/api/v1/accounts/1/custom_attribute_definitions', {
  method: 'POST',
  headers: { 'api_access_token': 'YOUR_TOKEN', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    attribute_display_name: 'Subscription Plan',
    attribute_display_type: 'list',
    attribute_model: 'contact_attribute'
  })
});
const data = await resp.json();
Request
import requests

resp = requests.post('https://api.supportly.io/api/v1/accounts/1/custom_attribute_definitions',
    headers={'api_access_token': 'YOUR_TOKEN'},
    json={'attribute_display_name': 'Subscription Plan',
          'attribute_display_type': 'list',
          'attribute_model': 'contact_attribute'})
data = resp.json()

Reports

Access analytics and performance reports for agents, inboxes, and teams.

GET/api/v2/accounts/{account_id}/reports/agents

Get agent performance metrics for a date range.

Query Parameters
sincestringrequiredStart date (ISO 8601)
untilstringrequiredEnd date (ISO 8601)
GET/api/v2/accounts/{account_id}/reports/inboxes

Get inbox-level performance metrics.

GET/api/v2/accounts/{account_id}/reports/teams

Get team-level performance metrics.

Get Agent Reports
Request
curl "https://api.supportly.io/api/v2/accounts/1/reports/agents?since=2024-01-01&until=2024-01-31" \
  -H "api_access_token: YOUR_TOKEN"
Request
require 'net/http'
require 'json'

uri = URI("https://api.supportly.io/api/v2/accounts/1/reports/agents?since=2024-01-01&until=2024-01-31")
req = Net::HTTP::Get.new(uri)
req['api_access_token'] = 'YOUR_TOKEN'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Request
const resp = await fetch('https://api.supportly.io/api/v2/accounts/1/reports/agents?since=2024-01-01&until=2024-01-31', {
  headers: { 'api_access_token': 'YOUR_TOKEN' }
});
const data = await resp.json();
Request
import requests

resp = requests.get('https://api.supportly.io/api/v2/accounts/1/reports/agents',
    headers={'api_access_token': 'YOUR_TOKEN'},
    params={'since': '2024-01-01', 'until': '2024-01-31'})
data = resp.json()

MCP Server

The Supportly MCP (Model Context Protocol) Server gives AI assistants like Claude, Cursor, and any MCP-compatible client full read and write access to your Supportly account. It exposes 78 tools across conversations, contacts, agents, reports, Captain AI, automations, and integrations โ€” all over a single HTTP endpoint.

MCP EndpointPOST https://app.supportly.io/mcp  |  Protocol version: 2024-11-05
Note — The MCP server uses your existing API access token. The same token that authenticates REST API calls works here.

Capabilities

tools78 tools covering every resource in Supportly โ€” with full read and write access
resourcesLive resources: Support Radar, All Agents
promptsBuilt-in prompt templates: support_briefing, contact_summary
batchingSupports batched JSON-RPC 2.0 requests in a single HTTP call
Server Info
GET /mcp
curl "https://app.supportly.io/mcp"
Response
{
  "name": "Supportly MCP Server",
  "version": "1.0.0",
  "protocol": "2024-11-05",
  "tool_count": 78,
  "endpoints": {
    "mcp": { "method": "POST", "path": "/mcp" },
    "mcp_account": { "method": "POST", "path": "/mcp/accounts/:account_id" }
  }
}

MCP Authentication

The MCP server uses the same API access token as the REST API. Pass it as the api_access_token or x-api-key header on every request.

Headers
api_access_tokenrequiredYour agent API access token. Find it in Profile Settings โ†’ Access Token.
x-api-keyAlias for api_access_token โ€” use whichever your MCP client supports.
x-account-idOptional. Override the account to use. If omitted, the first account linked to your token is used.
The account is automatically derived from your token. For multi-account setups, use the account-specific endpoint: POST /mcp/accounts/:account_id
Authentication
Initialize
curl -X POST "https://app.supportly.io/mcp" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2024-11-05",
      "clientInfo": { "name": "my-client", "version": "1.0" },
      "capabilities": {}
    }
  }'
Response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": { "tools": {}, "resources": {}, "prompts": {} },
    "serverInfo": { "name": "Supportly MCP", "version": "1.0.0" }
  }
}

Connecting Clients

Any MCP-compatible client can connect to the Supportly MCP server. Below are setup instructions for the most common clients.

Claude Desktop

Add the following to your claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/, Windows: %APPDATA%\Claude\):

Cursor

Add to your Cursor MCP settings at ~/.cursor/mcp.json:

Direct HTTP

Any HTTP client can call the MCP endpoint directly using JSON-RPC 2.0 over POST.

After connecting, restart your AI client and try asking:
"Use get_support_radar to show me what's happening in support right now."
Claude Desktop Config
claude_desktop_config.json
{
  "mcpServers": {
    "supportly": {
      "command": "npx",
      "args": ["-y", "mcp-remote",
        "https://app.supportly.io/mcp"],
      "env": {
        "MCP_API_KEY": "YOUR_API_ACCESS_TOKEN"
      }
    }
  }
}
Cursor Config
~/.cursor/mcp.json
{
  "mcpServers": {
    "supportly": {
      "url": "https://app.supportly.io/mcp",
      "headers": {
        "api_access_token": "YOUR_API_ACCESS_TOKEN"
      }
    }
  }
}
List All Tools
tools/list
curl -X POST "https://app.supportly.io/mcp" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'

Conversation Tools

Full read and write access to conversations. List, search, create, reply, assign, label, prioritise, mute, and bulk-update conversations.

Available Tools
list_conversationsList conversations with filters: status, assignee, inbox, team, label, priority, page
get_conversationFull conversation detail with last 20 messages, contact info, assignee, labels, timeline
create_conversationCreate a new conversation for a contact in a specific inbox
send_messageSend a reply or private note in a conversation
assign_conversationAssign to an agent and/or team (pass 0 to unassign)
update_conversation_statusSet status: open, resolved, pending, snoozed (with optional snooze datetime)
add_conversation_labelAdd a label to a conversation
remove_conversation_labelRemove a label from a conversation
update_conversation_prioritySet priority: low, medium, high, urgent
mute_conversationMute or unmute a conversation
search_conversationsFull-text search across conversation messages and contact info
get_conversation_messagesGet all messages with sender info, timestamps, and type
bulk_assign_conversationsAssign multiple conversations to an agent/team at once
bulk_update_conversation_statusBatch status update across multiple conversations
bulk_add_labelAdd a label to multiple conversations at once
Call a Tool
tools/call โ€” list_conversations
curl -X POST "https://app.supportly.io/mcp" \
  -H "api_access_token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "list_conversations",
      "arguments": {
        "status": "open",
        "priority": "urgent",
        "assignee_type": "unassigned"
      }
    }
  }'
send_message
{
  "method": "tools/call",
  "params": {
    "name": "send_message",
    "arguments": {
      "conversation_id": 42,
      "content": "Hi! I can help with that.",
      "private": false
    }
  }
}

Contact Tools

Full CRUD for contacts including notes, conversation history, merging, and custom attributes.

Available Tools
list_contactsList contacts with search, company filter, and sorting
get_contactFull contact profile: details, recent conversations, notes, custom attributes
create_contactCreate a contact with name, email, phone, company, location, and custom attributes
update_contactUpdate any contact field including additional attributes
search_contactsSearch by name, email, phone, or company
merge_contactsMerge two contacts โ€” child is merged into parent and deleted
get_contact_conversationsAll conversations for a contact with optional status filter
add_contact_noteAdd a note to a contact
get_contact_notesList all notes for a contact with author and timestamp
delete_contact_noteRemove a note from a contact
Examples
get_contact
{
  "method": "tools/call",
  "params": {
    "name": "get_contact",
    "arguments": { "id": 1234 }
  }
}
add_contact_note
{
  "method": "tools/call",
  "params": {
    "name": "add_contact_note",
    "arguments": {
      "contact_id": 1234,
      "content": "Spoke with their CEO. Interested in enterprise plan."
    }
  }
}

Agent & Team Tools

Read agent workloads, availability, team compositions, and inbox configuration.

Available Tools
list_agentsAll agents with roles, availability, open conversation count, and team memberships
get_agentAgent detail with full workload breakdown: open, pending, snoozed, resolved today
list_teamsAll teams with member counts and open conversation counts
get_teamTeam detail with all members listed
list_team_agentsAll agents in a specific team
list_inboxesAll inboxes with channel type, open count, and config (auto-assign, working hours)
get_inboxInbox detail with members, stats, and configuration
Example
list_agents
{
  "method": "tools/call",
  "params": {
    "name": "list_agents",
    "arguments": { "team_id": 3 }
  }
}

Report Tools

Real-time support health and historical performance reports โ€” by account, agent, team, inbox, or label.

Available Tools
get_support_radarReal-time snapshot: open/pending/snoozed counts, unassigned, urgent conversations, agent workload, most recent activity
get_account_summaryAccount-level performance for a date range: volume, resolution rate, avg resolution time, CSAT
get_csat_summaryCSAT score breakdown: Satisfied / Neutral / Dissatisfied counts and percentages
list_csat_responsesIndividual CSAT survey responses with rating, feedback, contact, and agent
get_agent_reportPer-agent performance: conversations handled, resolved, CSAT score
get_team_reportPer-team performance for a date range
get_inbox_reportPer-inbox volume and resolution stats
get_label_reportConversation volume breakdown by label
Support Radar is also available as a live MCP resource at URI supportly://radar โ€” useful for polling tools.
Support Radar
get_support_radar
{
  "method": "tools/call",
  "params": {
    "name": "get_support_radar",
    "arguments": {}
  }
}

// Returns:
"""
=== SUPPORTLY SUPPORT RADAR ===
Generated: 2025-01-15 09:30 UTC

๐Ÿ“Š VOLUME
  Open: 47 | Pending: 12 | Snoozed: 3
  Unassigned (open): 8
  Created today: 23 | Resolved today: 19

๐Ÿšจ PRIORITY ALERTS
  Urgent conversations: 2
  High priority: 7
  ...
"""

Automation Tools

Full CRUD for automation rules โ€” create, update, enable/disable, and delete rules that trigger on conversation events.

Available Tools
list_automation_rulesAll rules with trigger events, conditions, actions, and active status
get_automation_ruleFull rule detail with all conditions and actions listed
create_automation_ruleCreate a rule with event, conditions array, and actions array
update_automation_ruleUpdate any fields of an existing rule
toggle_automation_ruleEnable or disable a rule without deleting it
delete_automation_rulePermanently delete a rule
Trigger Events

conversation_created ยท conversation_updated ยท conversation_status_changed ยท message_created ยท contact_created ยท contact_updated

Actions

assign_an_agent ยท assign_a_team ยท add_label ยท remove_label ยท send_message ยท mute_conversation ยท resolve_conversation ยท snooze_conversation

Create Automation
create_automation_rule
{
  "name": "create_automation_rule",
  "arguments": {
    "name": "Escalate billing urgency",
    "event": "message_created",
    "conditions": [{
      "attribute_key": "content",
      "filter_operator": "contains",
      "values": ["billing", "invoice"]
    }],
    "actions": [
      { "action_name": "add_label", "action_params": ["billing"] },
      { "action_name": "assign_a_team", "action_params": [2] }
    ],
    "active": true
  }
}

Captain AI Tools

Query and manage the Captain AI knowledge base. Search indexed documents, add new knowledge, and inspect assistants and scenarios.

Available Tools
list_captain_assistantsAll Captain AI assistants with document counts
get_captain_assistantAssistant detail including indexed vs pending document counts
query_captain_knowledgeSemantic search across indexed knowledge base documents
list_captain_documentsAll knowledge base documents with status (indexed / pending / failed)
create_captain_documentAdd a new document to the knowledge base (indexing starts automatically)
delete_captain_documentRemove a document from the knowledge base
list_captain_scenariosAll Captain automation scenarios
Query Knowledge Base
query_captain_knowledge
{
  "name": "query_captain_knowledge",
  "arguments": {
    "query": "refund policy for annual plans",
    "limit": 5
  }
}
create_captain_document
{
  "name": "create_captain_document",
  "arguments": {
    "name": "Refund Policy v2",
    "content": "Annual plan refunds are available within 30 days of renewal..."
  }
}

Content Tools

Manage labels, canned responses (saved reply templates), and outgoing webhooks.

Labels
list_labelsAll labels with colors and conversation usage counts
create_labelCreate a label with title, description, and hex color
update_labelUpdate label title, description, or color
Canned Responses
list_canned_responsesAll saved reply templates with optional search by short code or content
create_canned_responseCreate a new template with short code and content
update_canned_responseUpdate short code or content
delete_canned_responseDelete a template
Webhooks
list_webhooksAll outgoing webhooks with subscribed events
create_webhookCreate a webhook with URL and event subscriptions
delete_webhookDelete a webhook
Examples
create_canned_response
{
  "name": "create_canned_response",
  "arguments": {
    "short_code": "refund30",
    "content": "Refunds are available within 30 days of purchase. I've initiated yours now."
  }
}
create_webhook
{
  "name": "create_webhook",
  "arguments": {
    "url": "https://your-app.com/hooks/supportly",
    "subscriptions": [
      "conversation_created",
      "message_created",
      "conversation_status_changed"
    ]
  }
}

Integration Tools

Query connected integrations and get a unified 360ยฐ view of any customer across all connected services โ€” CRM, billing, bookings, and more.

Available Tools
list_integrationsAll available integrations with connection status, sync state, and last sync timestamp
get_integration_statusDetailed status for a specific integration (Pipedrive, HubSpot, Schedly, Stripe, etc.)
get_customer_360Unified customer view: support history, notes, CRM data, billing, bookings โ€” all in one call
trigger_integration_syncManually trigger a data sync for a connected integration
Supported Providers

pipedrive hubspot salesforce schedly zendesk stripe shopify slack gong fireflies confluence linear jira calendly

Customer 360ยฐ
get_customer_360
{
  "name": "get_customer_360",
  "arguments": { "contact_id": 1234 }
}

// Returns a unified profile:
"""
=== CUSTOMER 360ยฐ VIEW ===
Contact: Jane Smith (ID: 1234)
Email: jane@acme.com | Phone: +1 555 0100
Company: Acme Corp

โ”€โ”€โ”€ SUPPORT HISTORY โ”€โ”€โ”€
Total conversations: 14
Open: 2 | Resolved: 12

โ”€โ”€โ”€ INTEGRATION DATA โ”€โ”€โ”€
Pipedrive:
  Deal stage: Negotiation | Value: $12,000
Stripe:
  Customer ID: cus_xxx | Plan: Business
Schedly:
  Next booking: Jan 20 at 2pm
"""

ChatGPT GPT Actions

Build a custom GPT inside ChatGPT that has live access to your Supportly account. GPT Actions work by pointing ChatGPT at a hosted OpenAPI spec โ€” Supportly provides one ready to use. No coding required.

How to set it up

1Go to chatgpt.com โ†’ Explore GPTs โ†’ Create
2Click Configure โ†’ scroll to Actions โ†’ click Create new action
3Paste the schema URL below into the Import from URL field
4Set Authentication to API Key, type api_access_token, paste your token
5Add to Instructions: "My Supportly account ID is [YOUR_ACCOUNT_ID]. Always use this in API calls."
6Save and publish. Your custom GPT can now talk to Supportly.
Schema URLhttps://app.supportly.io/mcp/schemas/chatgpt
Account ID — Find your account ID in Supportly under Settings โ†’ General Settings. You must include it in your GPT instructions.
Covered Operations
ConversationsList, get, create, send message, assign, update status/priority, add label
ContactsList, get, create, update, search, add note, get notes
Agents & TeamsList agents, list teams, list inboxes
ContentList labels, list canned responses
Schema URL
Import this URL into ChatGPT
https://app.supportly.io/mcp/schemas/chatgpt
GPT Instructions Template
Paste into GPT Instructions field
You are a Supportly assistant with live access to my
customer support account.

My account ID is: [YOUR_ACCOUNT_ID]

Always include account_id in every API request.
When asked about support health, list open conversations
grouped by priority. Confirm before resolving or
reassigning conversations.

You can:
- View and manage conversations
- Search and update contacts
- Assign conversations to agents or teams
- Send replies or private notes
- Look up canned responses
Example Prompts
What you can ask
// In ChatGPT after connecting:

"Show me all urgent open conversations"
"Find the contact Jane Smith and add a note"
"Resolve conversation 142 and send a closing message"
"Who are our available agents right now?"
"Search for a canned response about refunds"

Gemini Gems

Connect Supportly to Google Gemini by creating a custom Gem. Gemini Gems support OpenAPI-based function calling โ€” Supportly provides a ready-made spec. You can also use it directly via Google AI Studio or Vertex AI for developer integrations.

Consumer Gem Setup (gemini.google.com)

1Go to gemini.google.com โ†’ Gems โ†’ New Gem
2Give it a name like "Supportly Assistant"
3Paste the instructions template (right) into the Instructions field
4Save and chat. Ask it about your support queue, contacts, and conversations.
Note — Consumer Gems do not yet support live API calls. They work as a knowledgeable assistant based on your instructions. For live API access, use Google AI Studio or Vertex AI with the function spec below.

Developer Setup (AI Studio / Vertex AI)

1Go to aistudio.google.com โ†’ create a new prompt
2Enable Function Calling and add the Supportly function spec
3Fetch our function definitions from the schema URL and add them to your declarations
4In your app, handle function call responses by forwarding to https://app.supportly.io/api/v1
Schema URLhttps://app.supportly.io/mcp/schemas/gemini
Gem Instructions Template
Paste into Gemini Gem Instructions
You are a Supportly customer support assistant.

Supportly is our customer support platform. Here is
what you know about how to help:

- Conversations are support tickets. They can be
  open, pending, resolved, or snoozed.
- Contacts are customers. Each has a name, email,
  phone, and conversation history.
- Agents are support team members. Teams group agents.
- Canned responses are saved reply templates.
- Priority levels: urgent, high, medium, low.

When asked to check on support, describe what
a healthy vs unhealthy queue looks like: low
unassigned conversations, no urgent items waiting,
all agents have reasonable workloads.

Always be concise and actionable.
Developer: Function Calling (Node.js)
AI Studio Function Calling
const { GoogleGenerativeAI } = await import('@google/generative-ai');

// Fetch Supportly function declarations
const schema = await fetch(
  'https://app.supportly.io/mcp/schemas/gemini'
).then(r => r.json());

const genAI = new GoogleGenerativeAI('YOUR_GEMINI_KEY');
const model = genAI.getGenerativeModel({
  model: 'gemini-1.5-pro',
  tools: [{ functionDeclarations: schema.functions }]
});

const chat = model.startChat();
const result = await chat.sendMessage(
  'Show me all urgent open conversations'
);

// Handle function calls
const call = result.response.functionCalls()?.[0];
if (call) {
  const resp = await fetch(
    `https://app.supportly.io/api/v1/${call.args.account_id}/conversations`,
    { headers: { 'api_access_token': 'YOUR_TOKEN' } }
  );
  // Feed result back to Gemini...
}