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 guideQuickstart
- 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. - 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.
- 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/mcpJSON 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
- Discover changes: bootstrap with the durable
syncfeed, save nextCursor after processing each page, and deduplicate event IDs. To pick work, calllist_conversationswith sourceId, awaitingReply: true, snoozed: "false", and compact: true. Internal notes do not hide unanswered customer messages. - Read only new text: for each changed conversation, call
list_messageswith your last message ID or ISO timestamp inafterand setincludeHtml: falseunless the email HTML is actually needed. - Load customer context:
get_conversationreturnsidentityVerified, signedverifiedTraits, and the conversation’s origin page and client context. When it has acontact.id, pass that ID tolist_conversationsto load the customer’s earlier conversations. - Wake on demand: use a source-filtered webhook for
message.createdto 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.
- Start without a cursor, follow nextCursor until hasMore is false, and save that cursor.
- Read the current conversation/contact lists and any history you need for your initial state.
- 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.
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.
- Choose your channel’s sourceId and grant kb:read and kb:write. For optional discovery,
list_sourcesalso needs conversations:read. - Create the category, then sync an article as a draft. Review its formatting and access before publishing.
- Configure help-center access, publish, and test the reader view. Use pagination and stable external IDs for later updates.
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