Developers

Sonny MCP Beta

Connect MCP-compatible AI assistants — Claude, and any client that speaks the Model Context Protocol — to your workspace, with the same scopes and boundaries as the public API.

Read the Public API guide

Quickstart

  1. 01

    Add Sonny to Claude

    In Claude or Cowork, add a custom connector with the URL https://www.usesonny.com/api/mcp. Sonny supports automatic client registration, so there is no client ID or secret to copy.

  2. 02

    Approve workspace access

    Claude opens Sonny in your browser. Sign in, review the requested permissions, and choose the workspace to connect. OAuth 2.1 with PKCE keeps the resulting access and refresh tokens scoped to that approval.

  3. 03

    Ask your assistant to use Sonny

    Tools describe themselves, so a prompt like “List my open conversations” is enough. Assistants see which tools are read-only and which are destructive, so a good client asks before it changes anything.

Claude Code

claude mcp add --transport http sonny https://www.usesonny.com/api/mcp

JSON client configuration

{
  "mcpServers": {
    "sonny": {
      "url": "https://www.usesonny.com/api/mcp"
    }
  }
}

API key compatibility

If a client cannot complete OAuth, create a scoped key in Settings → Developer and send it as an Authorization Bearer header. API keys remain fully supported for server-to-server and legacy clients.

"Authorization": "Bearer sonny_your_key"

How Sonny keeps tool calls safe

One workspace, chosen scopes
Every call runs inside the workspace chosen during OAuth consent or the workspace that owns the API key. A tool only works when its credential holds the required scope.
Honest tool annotations
Read-only tools are marked read-only; updating and deleting tools are marked destructive so your client can confirm before acting.
The same business rules
Tools run the exact workflows the public API runs — audit history, notifications, and webhooks all behave as if a teammate made the change.

Keep an assistant inside one inbox

Selected channels on an API key are automatically enforced across every tool call. API keys and OAuth connections also follow the connected teammate's current channel access. For OAuth connections or keys with access to all channels, workspaces often hold several sources — one per product or brand. Have your assistant call list_sources once to discover them, then pass sourceId to list_conversations so questions about one product only ever return that product's conversations. Every conversation also carries its own sourceId, so results are verifiable.

Run a low-cost support loop

  1. Discover changes: bootstrap with the durable sync feed, save nextCursor after processing each page, and deduplicate event IDs. To pick work, calllist_conversations with sourceId, awaitingReply: true, snoozed: "false", and compact: true. Internal notes do not hide unanswered customer messages.
  2. Read only new text: for each changed conversation, call list_messages with your last message ID or ISO timestamp in after and set includeHtml: false unless the email HTML is actually needed.
  3. Load customer context: get_conversation returns identityVerified, signed verifiedTraits, and the conversation’s origin page and client context. When it has a contact.id, pass that ID to list_conversations to load the customer’s earlier conversations.
  4. Wake on demand: use a source-filtered webhook for message.created to wake the agent immediately, then catch up with sync. The durable feed works independently of webhook delivery. Use an all-channel credential to provision the webhook and restrict its source IDs; the agent keeps its source-scoped runtime credential.

Tool catalog

Every public API operation is available as a tool. The required scope is listed beside each one.

create_conversation
Create a conversation from a form
conversations:write
upload_attachment
Upload a message attachment
attachments:write
list_properties
List custom properties
contacts:read
get_contact_properties
Read contact properties
contacts:read
set_contact_property
Set a contact property
contacts:write
list_contact_memories
Read customer memories
contact-memory:read
create_contact_memory
Save a customer memory
contact-memory:write
delete_contact_memory
Remove a customer memory
contact-memory:write
get_report
Read a support report
reporting:read
sync
Read workspace changes
conversations:read
batch_update_conversations
Update conversations in a batch
conversations:write
list_members
List members
members:read
list_teams
List teams
members:read
list_tags
List tags
tags:read
create_tag
Create a tag
tags:write
get_tag
Get a tag
tags:read
update_tag
Update a tag
tags:write
delete_tag
Delete a tag
tags:write
add_conversation_tag
Add a conversation tag
conversations:write
remove_conversation_tag
Remove a conversation tag
conversations:write
list_contacts
List contacts
contacts:read
create_contact
Create a contact
contacts:write
get_contact
Get a contact
contacts:read
update_contact
Update a contact
contacts:write
archive_contact
Archive a contact
contacts:write
list_channels
List conversation channels
conversations:read
list_sources
List sources
conversations:read
list_conversations
List conversations
conversations:read
get_conversation
Get a conversation
conversations:read
update_conversation
Update a conversation
conversations:write
list_messages
List messages
messages:read
create_internal_note
Create an internal note
messages:write
send_message
Send a reply to the customer
messages:send
list_webhooks
List webhook endpoints
webhooks:read
create_webhook
Create a webhook endpoint
webhooks:write
list_webhook_events
List webhook event types
webhooks:read
update_webhook
Update a webhook endpoint
webhooks:write
delete_webhook
Delete a webhook endpoint
webhooks:write
test_webhook
Queue a test event
webhooks:write
list_webhook_deliveries
List webhook deliveries
webhooks:read
retry_webhook_delivery
Retry a failed delivery
webhooks:write
list_kb_articles
List knowledge base articles
kb:read
create_kb_article
Create a knowledge base article
kb:write
get_kb_article
Get a knowledge base article
kb:read
update_kb_article
Update a knowledge base article
kb:write
delete_kb_article
Delete a knowledge base article
kb:write
list_kb_categories
List knowledge base categories
kb:read
create_kb_category
Create a knowledge base category
kb:write
update_kb_category
Update a knowledge base category
kb:write
delete_kb_category
Delete a knowledge base category
kb:write
get_csat_summary
Get CSAT scores
csat:read
list_csat_ratings
List CSAT ratings
csat:read
get_conversation_csat
Get a conversation's CSAT rating
csat:read
reorder_kb_articles
Reorder knowledge base articles
kb:write
upsert_kb_article
Sync a knowledge base article
kb:write
reorder_kb_categories
Reorder knowledge base categories
kb:write
upsert_kb_category
Sync a knowledge base category
kb:write
get_kb_category
Get a knowledge base category
kb:read
get_help_center
Get help center settings
kb:read
update_help_center
Update help center settings
kb:write
upload_kb_media
Upload a help center image
kb:write
list_kb_audiences
List audiences
kb:read
create_kb_audience
Create audience
kb:write
update_kb_audience
Update audience
kb:write
delete_kb_audience
Delete audience
kb:write

Agent workflows

Triage, act, and stay in sync

REST and MCP share the same permissions and workflows. Credentials can only narrow your current channel access; removing a channel from your membership also removes it from your integrations.

Find conversations that need attention

Filter by awaitingReply, unread, unassigned, assigneeId, agentGroupId, snoozed, priority, or tagIds. Awaiting reply ignores internal notes; unread is specific to the authenticated teammate. Unassigned means no individual assignee, even if a team is assigned. Tags match any supplied ID.

list_conversations({ awaitingReply: true, snoozed: "false", compact: true, sort: "priority", direction: "desc" })

Sort by lastMessageAt, createdAt, or business priority with direction asc or desc. IDs break ties. Omitted filters retain compact=false and snoozed=any. REST tagIds accepts comma-separated IDs; MCP also accepts an array.

Catch up without rereading the inbox

Use sync with conversations:read. The durable feed includes conversation changes, tags, messages, contact properties, memories, and deletion records. Each payload also requires its own read scope: message bodies need messages:read and memories need contact-memory:read. Restricted credentials receive only permitted channels.

  1. Start without a cursor, follow nextCursor until hasMore is false, and save that cursor.
  2. Read the current conversation/contact lists and any history you need for your initial state.
  3. Replay from the saved cursor to catch changes during that read. Process each page, then save nextCursor.

Deduplicate by event id: replay is at least once, so downstream actions need their own duplicate protection. A request without a cursor covers the last hour, not a complete snapshot. Events are retained for 30 days; HTTP 410 resync_required means bootstrap again. Also bootstrap after widening scopes or channel access. Use limit up to 100 and maxBodyChars up to 10,000 (default 500). Webhooks can wake an agent; the feed remains available without webhook subscriptions or when webhook delivery is backlogged.

Assign and update work in batches

Discover active, assignable teammates and teams with list_members / list_teams (members:read). Responses include availability and effective channel access, without email addresses. Use conversations:write to change status, priority, assigneeId, agentGroupId, snoozedUntil, snoozedUntilReply, addTagIds, or removeTagIds.

batch_update_conversations({
  conversationIds: ["conversation_1", "conversation_2"],
  updates: { agentGroupId: "team_1", assignment: "round_robin" }
})

Round-robin chooses an available team member who can access the conversation's channel. With no eligible member, a single update returns 409 and a batch item fails. Each batch accepts up to 100 unique IDs and applies one patch atomically per conversation. Inspect every { id, ok, error? } result; some items may fail. Batches use the current request-based rate limit, without weighted charging by item count.

Share customer context with Sonny AI

List, save, and remove contact memories with contact-memory:read/write. Supply both contactId and an accessible sourceId; the contact must have a conversation on that source. Saved facts use the same validation, duplicate handling, and limits as the app and are recorded as manual memories.

Read property definitions at /api/v1/properties and read or set values at /api/v1/contacts/{contactId}/properties. MCP exposes list_properties, get_contact_properties, and set_contact_property. These require contacts:read/write and access to all channels. Set exactly one propertyId or propertyName, with a string value or null to clear it. Text, number, URL, date, and select values are validated against the field definition.

set_contact_property({ contactId: "contact_1", propertyName: "Plan", value: "Pro" })
list_contacts({ property: { Plan: "Pro" } })

Property filters match exact stored strings; multiple properties must all match. API-authored values are labelled as API data in Co-Pilot and the auto-responder. Existing verified-customer and source rules still apply. Ordinary manual internal field values remain excluded from this AI context.

Read the same reports as your team

With reporting:read, call get_report({ kind, filters }). Kinds are overview, volume, response-times, agents, sources, tags, csat, ai, knowledge, leads, and online-hours. Overview combines counts, response times, and CSAT. Filters are period (1–365 days, default 30), paired from/to dates (to is exclusive), granularity (day/month), source, assigneeId, and tagId. Source accepts all, all-chat, all-email, website:{id}, or email:{id}.

Reports use the dashboard queries and current access checks. Knowledge uses dates and permitted channels, independently of the selected source, assignee, or tag. Online hours measures accessible teammates' workspace presence; its conversation counts remain channel-scoped. Existing /csat endpoints keep their separate csat:read scope and may describe a different population.

Send files with replies or internal notes

Grant attachments:write and upload a file up to 25 MB for a conversation. upload_attachment takes conversationId, fileName, contentType, and dataBase64. The response contains an id and expiresAt. Pass up to 10 attachmentIds to a reply or note within one hour. Each upload is bound to your user and conversation and can be used only once. Unused uploads are cleaned up after expiry.

send_message({ conversationId: "conversation_1", attachmentIds: ["upload_1"] })

Replies require messages:send; internal notes require messages:write and never reach customers. Attachment-only messages are supported. Email replies include files within the email size budget and download links for the rest; offline chat emails include file links. Emailed links remain usable while the attachment exists, so recipients can open them later. Forwarding the email shares access to those files. Check emailDeliveryStatus for delivery failures.

Authorized message reads include downloadUrl, downloadExpiresAt, aiStatus, aiDescription, and aiExtractedText. Download links last 15 minutes; reread the message for fresh links. New API/MCP uploads have private storage. Older attachments remain public and are marked access=legacy_public: their original URL does not expire. Extraction is available when Sonny's configured AI features have analyzed a supported image; customer-facing widget and realtime messages never include that extraction.

Explore the full request and response contracts

Knowledge base

Sync from an external source

Sync articles and categories using stable external IDs, import Markdown or HTML, upload images, set ordering, and manage reader audiences. Repeating an upsert updates the existing content.

  1. Choose your channel’s sourceId and grant kb:read and kb:write. For optional discovery, list_sources also needs conversations:read.
  2. Create the category, then sync an article as a draft. Review its formatting and access before publishing.
  3. Configure help-center access, publish, and test the reader view. Use pagination and stable external IDs for later updates.
Follow the complete MCP sync walkthrough

Manage customer audiences

Create groups from verified customer traits and apply them to a help center, category, or article. All inherited restrictions must match. API and MCP credentials act as staff within their scopes; preview and test a real customer session to check reader access.

Set up and test help-center audiences

Related docs