API Reference
Public Conversations
💬Get free consultation

Conversations

ⓘ

Who can use this feature?

  • An admin on Pro or Plus creates the API key. Every endpoint below requires an explicit scope; legacy keys without scopes are refused.
  • Reads require conversations:read, ordinary writes require conversations:write, and deleting a conversation requires conversations:delete. Write access includes read access, but does not include deletion.

Use these paths on the Public API base URL. Send X-Api-Key on every request and Content-Type: application/json for JSON writes. The upload endpoint uses multipart instead. The Chat Conversations API keeps its existing /chat routes; starting, uploading, and deleting a conversation are Public API operations only.

Start an email conversation

POST /conversations — conversations:write, Pro / Plus. Limited to 60 starts per hour per shop, shared with MCP.

This starts an outbound email conversation. It does not create an anonymous storefront chat or start a WhatsApp template conversation. An email address is required even if you omit the first message.

FieldRequiredMeaning
emailYesRecipient email; trimmed and lowercased. An existing contact is reused, otherwise a guest contact is created.
memberIdNoActive member of the same shop. Defaults to the member who created the API key; pass it explicitly if that identity is unavailable.
subjectNoEmail subject, up to 998 characters, without line breaks. Defaults to an empty string.
participantsNoUp to 5 email addresses before deduplication. Addresses are trimmed and lowercased. Defaults to [].
messageNoFirst message, nonblank if supplied, up to 5,000 characters after trimming.
filesNoUp to 10 existing shop-owned file descriptors, in the format described below. REST does not accept base64 files here.
curl -X POST 'https://app.chatty.net/v1/conversations' \
  -H 'X-Api-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -d '{"email":"[email protected]","memberId":"member_123","subject":"Your order","message":"Your order is ready."}'

A 201 response contains the new ID and, when a first message was supplied, the saved message. Example excerpt:

{"data":{"conversationId":"conv_123","message":{"id":"msg_123","text":"Your order is ready.","isAdmin":true,"memberId":"member_123"}}}

Without a message or files, the response is {"data":{"conversationId":"conv_123"}}; no customer-facing first message is sent. No Shopify customer is created by this operation.

Invalid emails, subject, member, message, or file descriptors return 400 INVALID_PARAMS. A missing shop returns 404 NOT_FOUND. Shared authentication, scope and rate errors are listed below.

!

A 502 can happen after the conversation exists. SEND_FAILED or UPLOAD_FAILED can include error.conversationId. Keep that ID, inspect its messages, and recover on the existing conversation. Repeating POST /conversations can create a second conversation.

{"error":{"code":"SEND_FAILED","message":"Message could not be sent","conversationId":"conv_123"}}

Upload attachments

POST /conversations/{id}/uploads — conversations:write, Pro / Plus. The conversation must belong to the key's shop. Limited to 60 upload batches per hour per shop, shared with MCP.

curl -X POST 'https://app.chatty.net/v1/conversations/conv_123/uploads' \
  -H 'X-Api-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  -F '[email protected];type=application/pdf'

Let your HTTP client set the multipart boundary. Send 1–10 files, at most 20 MiB per file, and at most 25 MiB for the complete multipart request. Uploading does not send a message. 201 returns descriptors only when the whole batch succeeds:

{"data":[{"id":"file_123","url":"livechat/shop_123/conversations/file_123.pdf","mimeType":"application/pdf","title":"invoice.pdf","altText":""}]}

This is an example descriptor; use the exact values returned for your shop. A partial or complete storage failure returns 502 UPLOAD_FAILED, not a successful partial batch. Some files may already have reached storage; check before retrying a failed batch. Missing or foreign conversations return 404 NOT_FOUND. Invalid multipart input, size, count or MIME type returns 400 INVALID_PARAMS.

Accepted types include JPEG, PNG, GIF, WebP, PDF, DOC/DOCX, XLS/XLSX, PPT/PPTX, plain text, CSV, supported video and archive formats. SVG is not accepted. These are the exact MIME values:

image/jpeg, image/png, image/gif, image/webp
application/pdf, application/msword
application/vnd.openxmlformats-officedocument.wordprocessingml.document
application/vnd.ms-excel, application/x-excel, application/x-msexcel, application/x-vnd.ms-excel
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
application/vnd.ms-powerpoint
application/vnd.openxmlformats-officedocument.presentationml.presentation
text/plain, text/csv
video/mp4, video/webm, video/quicktime, video/x-msvideo, video/3gpp, video/x-matroska, video/mpeg, video/ogg
application/zip, application/x-rar, application/x-rar-compressed, application/vnd.rar
application/x-7z, application/x-7z-compressed, application/gzip, application/x-gzip
application/x-bzip, application/x-bzip2, application/x-tar, application/x-tar-gz, application/x-tgz
application/x-compress, application/x-compressed, application/x-lzip, application/x-xz
application/octet-stream, application/x-zip, application/x-zip-compressed, application/zip-compressed

Send a reply or attachment

POST /conversations/{id}/messages — conversations:write, Pro / Plus. The same request works at POST /chat/conversations/{id}/messages.

FieldMeaning
textUp to 5,000 characters after trimming. A nonblank text or at least one valid attachment is required.
memberIdRequired for an agent reply or internal note; active member of this shop.
isNotetrue creates an internal message, not a customer-facing reply.
asCustomertrue relays an incoming customer message; ignores memberId and isNote.
filesArray of up to 10 shop-owned descriptors returned by upload. Attachment-only messages are allowed.

Each descriptor requires url and a supported mimeType. Optional id, title, and altText must be strings when present. Arbitrary external URLs and another shop's files are rejected. Accepted locations are this shop's Chatty upload keys or corresponding CDN URLs, and Shopify Files URLs belonging to the same Shopify shop. Ownership is checked per shop, not per conversation. Reuse returned descriptors instead of constructing paths yourself.

curl -X POST 'https://app.chatty.net/v1/conversations/conv_123/messages' \
  -H 'X-Api-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -d '{"memberId":"member_123","text":"Here is your invoice.","files":[{"id":"file_123","url":"livechat/shop_123/conversations/file_123.pdf","mimeType":"application/pdf","title":"invoice.pdf","altText":""}]}'

Success is 201 with data containing the saved message, including its ID and files. Message reads resolve stored upload keys into usable URLs. Invalid content, member, or files returns 400 INVALID_PARAMS; an unavailable conversation returns 404 NOT_FOUND. Replies to existing Messenger, Instagram, WhatsApp, or email conversations use those channels; outside the supported 24-hour messaging window, a reply can return 409 CHANNEL_UNAVAILABLE. An upload succeeding does not guarantee the channel can deliver every attachment type.

Internal messages stay internal. Customer-facing replies emit message.created; check data.senderType to avoid replying to your own agent or bot messages. There is no idempotency key for message sends.

Read conversations and messages

All routes in this table use conversations:read, Pro / Plus, and have no request body. Successful reads return 200; examples are excerpts or empty results. See Objects and fields for full field descriptions.

MethodPathQuery / request exampleResponse example
GET/conversations?status=open&limit=20{"data":[],"paging":{"nextCursor":null,"hasMore":false},"meta":{"source":"postgresql"}}
GET/conversations/{id}id=conv_123{"data":{"id":"conv_123","status":"open"},"meta":{"source":"postgresql"}}
GET/conversations/{id}/messages?order=asc&limit=50{"data":[],"paging":{"nextCursor":null,"hasMore":false}}
GET/conversations/{id}/messages/{messageId}messageId=msg_123{"data":{"id":"msg_123","text":"Hello","files":[]}}
GET/conversations/{id}/related?limit=6{"data":[],"paging":{"nextCursor":null,"hasMore":false},"meta":{"source":"postgresql"}}
GET/conversations/{id}/attributesid=conv_123{"data":[]}
GET/conversations/{id}/browsed-pagesid=conv_123{"data":[]}
GET/conversations/{id}/summaryid=conv_123{"data":{"summary":"Customer asked about delivery."}}

The list also accepts channel, assigneeId (or unassigned), isRead, repeated tag values, customerUuid, email, dateFrom, dateTo, and cursor. Channels are online_store, email, facebook, instagram, or whatsapp, matched against the conversation's origin. Tags match any supplied tag. Date-only bounds filter creation time, with the final day included; timezone-bearing ISO timestamps filter last-message time inclusively. Use the same format for both bounds. See the filter reference.

Pagination uses paging.nextCursor. Conversations default to 20 and cap at 100, messages default to 50 and cap at 100, and related conversations default to 6 and cap at 50. Message order is asc or desc, default asc. Invalid filters, paging or cursors return 400 INVALID_PARAMS. A missing or foreign conversation/message returns 404 NOT_FOUND; a summary that has not been generated also returns 404.

Conversation lists cover migrated records, while detail reads can also find older records by ID. Message lists exclude deleted messages; single-message reads may return isDeleted: true. Browsed pages are session-scoped, retain the latest 50 pages for about 24 hours, and may be empty.

State, assignment and typing

These four POST routes use conversations:write, Pro / Plus, and return 200. Missing or foreign conversations return 404 NOT_FOUND; invalid state or member input returns 400 INVALID_PARAMS.

MethodPathJSON requestResponse example
POST/conversations/{id}/state{"status":"resolved"} or {"status":"open"}{"data":{"status":"resolved"}}
POST/conversations/{id}/assign{"memberId":"member_123"}{"data":{"memberId":"member_123"}}
POST/conversations/{id}/read{}{"data":{"isRead":true}}
POST/conversations/{id}/typing{"memberId":"member_123","active":true}{"data":{"actorKey":"agent_member_123"}}

To unassign, send {"memberId":null} to /assign; it returns the same value in data. Omitting memberId or sending an empty string does not unassign and returns 400 INVALID_PARAMS. Resolve member IDs with GET /members (conversations:read, Pro / Plus; no body; 200 {"data":[]} for an empty list).

Typing also accepts a display name. Send active: false when typing stops; there is no server expiration. Typing is transient and does not create a durable mutation record.

Tags, checklist notes and attributes

These routes use conversations:write, Pro / Plus, except the separate tag catalog read. Request and response examples are below; note and attribute objects are excerpts.

MethodPathRequestSuccess response
POST/conversations/{id}/tags{"tags":["vip"]}200 {"data":{"tags":["vip"]}}
DELETE/conversations/{id}/tags/{tag}No body; URL-encode tag200 {"data":{"tags":[]}}
POST/conversations/{id}/notes{"text":"Check delivery"}201 {"data":{"id":"note_123","text":"Check delivery"}}
PUT/conversations/{id}/notes/{noteId}{"text":"Delivery checked"}200 {"data":{"id":"note_123","text":"Delivery checked"}}
DELETE/conversations/{id}/notes/{noteId}No body200 {"data":{"deleted":true}}
POST/conversations/{id}/attributes{"name":"order","value":"123"}201 {"data":{"id":"attr_123","name":"order","value":"123"}}
PUT/conversations/{id}/attributes/{attributeId}{"value":"456"}200 {"data":{"id":"attr_123"}}
DELETE/conversations/{id}/attributes/{attributeId}No body200 {"data":{"deleted":true}}

Tags append and deduplicate; tags over 100 characters are dropped, and a request with no valid tags returns 400 INVALID_PARAMS. Read the store's conversation tag catalog with GET /tags (conversations:read, Pro / Plus; no body; 200 {"data":[]} when empty). This is separate from customer tags.

Checklist notes allow 150 characters. They are separate from internal message notes sent with isNote. Attribute names allow 255 characters and values allow 1,000; creation upserts by name. Invalid inputs return 400 INVALID_PARAMS; a missing conversation, note or attribute returns 404 NOT_FOUND. Removing an already absent tag is a successful no-op.

Export conversations and transcripts

Both routes use conversations:read, Pro / Plus, and return the file in the response; nothing is emailed.

MethodPathRequestResponse
POST/conversations/export{"filters":{"isResolved":true,"createdAfter":"2026-10-01T00:00:00Z"},"format":"csv","limit":500}200, JSON or CSV
GET/conversations/{id}/transcript?format=txt200, text or CSV file

POST /conversations/export returns one page of up to limit conversations (1–1,000, default 1,000), most recently updated first. There is no offset: to export more, narrow the date filters and call again. An empty body exports with the defaults. The body and filters are strict; an unknown field returns 400 VALIDATION_FAILED.

FilterTypeMeaning
typestring or arrayConversation type, such as anonymous, guest or customer
isResolved, isBot, isBlockbooleanResolved, handled by AI, blocked
tags / tagsAllarrayAny of / all of these tags
channelsarrayAny of these channels
memberId, assigneeIdstringAssigned member
customerEmail, customerUuidstringExact match
createdAfter, createdBefore, updatedAfter, updatedBefore, resolvedAfter, resolvedBeforestringISO 8601 date-time with an offset, such as 2026-10-01T00:00:00Z
searchTextstringUp to 500 characters, searched in customer name, email and last message
excludeIdsarrayConversation IDs to leave out

Arrays hold up to 100 values of up to 500 characters each. format is json (default) or csv.

{
  "success": true,
  "data": {
    "totalCount": 1342,
    "pagination": {"limit": 1000, "offset": 0, "hasMore": true},
    "conversations": [{"id": "conv_123", "firestoreId": "conv_123", "pgConvoId": "1234567890", "channels": ["chatbox"], "tags": []}]
  },
  "exportInfo": {"format": "json", "exportedAt": "2026-10-23T08:00:00.000Z", "totalExported": 1000, "totalAvailable": 1342}
}

The JSON example is an excerpt; each conversation carries its stored fields in camelCase. totalAvailable counts every match, totalExported the rows returned. CSV is sent as an attachment named conversations-YYYY-MM-DD.csv with the columns ID, Customer Name, Email, Status, Tags, Channels, Member, Created At, Updated At and Resolved At. Every cell is quoted, and cells that a spreadsheet could run as a formula start with '.

GET /conversations/{id}/transcript returns the customer-facing messages of one conversation. format is txt (default) or csv; any other query parameter returns 400 VALIDATION_FAILED. Text lines read <time> | <sender>: <message>. CSV has the columns Time, Sender and Message. Internal notes, activity lines and reminders are left out. Attachments appear as lines such as Sent 2 files. A missing or foreign conversation returns 404 NOT_FOUND.

If the export takes too long, the routes return 502 UPSTREAM_TIMEOUT; if the data store is unavailable, 502 UPSTREAM_UNAVAILABLE. Both are safe to retry because they change nothing.

Delete a conversation

DELETE /conversations/{id} — conversations:delete, Pro / Plus. No body. conversations:write alone cannot delete.

curl -X DELETE 'https://app.chatty.net/v1/conversations/conv_123' \
  -H 'X-Api-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
{"data":{"id":"conv_123","deleted":true}}
!

Deletion removes conversation data and can also delete its customer. The cleanup removes messages, activity, traces, and conversation-owned attributes. If this was the customer's last conversation, the contact is deleted too. Deletion finalizes eligible AI usage that has not yet been counted; it does not reset consumed usage. There is no undo endpoint.

Success is 200. A missing or foreign conversation returns 404 NOT_FOUND; a record without a deletable Firestore record returns 404 CONVERSATION_NOT_DELETABLE. A successful deletion emits conversation.removed with empty data. Repeating the delete after it succeeds returns 404.

Shared errors and retry rules

Every endpoint above can reject authentication or scope checks before its handler runs. Public API version validation and shared rate budgets also apply.

HTTPCodeMeaning
400INVALID_API_VERSIONInvalid X-Api-Version date.
401UNAUTHENTICATED / INVALID_API_KEYMissing, invalid, or revoked key.
403FORBIDDENRequired scope missing; legacy key is not accepted.
429RATE_LIMITEDShared API/IP limit or the operation's hourly limit reached.

Start/upload validation can return 400 INVALID_PARAMS or 400 VALIDATION_FAILED; storage/delivery failures use 502 UPLOAD_FAILED or 502 SEND_FAILED. Read the operation-specific guidance before retrying. Unexpected failures are server errors; retain the response for support. A timeout is not proof that a write failed. Inspect the saved resource before repeating non-idempotent creation or sending.