REST API: inbox and teams
Automate ticket routing and keep human ownership explicit.
Inbox automation works on tickets and teams, not on anonymous chat text. Before you assign a ticket, change its status or team, reply, add a note, close it, or resume AI, fetch the ticket and its thread. A takeover changes the ownership of a ticket. Show this change to the human team. Do not let competing workers repeatedly take over the same ticket.
Teams have their own membership lifecycle. Before you add or remove a person, read the team state and the member state. Use team deletion only for planned cleanup, after you reroute the tickets. Inbox list pagination differs from the common after cursor pattern. Read the route schema. Do not reuse a generic list helper.
Two routes read the agent behind the ticket, and both refuse an agent that waits for deletion with 404 AGENT_NOT_FOUND: POST /inbox/tickets/takeover and POST /inbox/tickets/{ticketId}/team. A refused team move keeps the current team and writes no ticket event. Assignment, status changes, replies, notes, and close do not read the agent, so they still work on the same ticket. Match on the error code and read the ticket again before you retry.
AI Compose and Copilot draft help a human workflow. They do not send a reply by themselves. Before your integration calls the ticket reply endpoint, a responsible actor must review the proposed content. Log ticket identifiers and error codes. This makes routing and permission failures easy to investigate.
Attach a file to a reply
POST /inbox/tickets/{ticketId}/attachments stores one file for a ticket and returns {uploadId, filename, contentType, sizeBytes}. Pass the uploadId in the attachmentUploadIds of POST /inbox/tickets/{ticketId}/reply. An upload id attaches to one message only. The route is also the MCP tool upload_attachment, and it needs a token with mcp:send that acts for a person. An API key gets 412 ACTOR_NOT_USER, the same refusal the reply route gives it, because a file uploaded with a key could never be sent.
Send exactly one of two bodies. {filename, contentType, dataBase64} carries the bytes inline, at most 10 MB once decoded. {sourceUrl, filename?} names an https URL that the server fetches, at most 25 MB, and takes the type from the response. The server refuses a URL that resolves to a private or internal address, re-checks each redirect, and follows at most three. The allowed types match the Inbox composer: images except SVG, plain text, and PDF. A PNG, JPEG, WebP or GIF must really be that image. Errors: 400 ATTACHMENT_SOURCE_INVALID (not exactly one body shape, bad base64, or a URL that is not https), 400 ATTACHMENT_SOURCE_BLOCKED (an internal address), 413 ATTACHMENT_TOO_LARGE, 415 ATTACHMENT_TYPE_NOT_ALLOWED, 502 ATTACHMENT_FETCH_FAILED, and 404 TICKET_NOT_FOUND.
A request body on the platform is limited in size, so a large file sent as base64 can be refused before it reaches the API. For a file over about 3 MB, prefer sourceUrl.
Find a ticket and check email delivery
GET /mcp/tickets/find finds a ticket by text, by customer email, or by the chat session that the ticket came from. Give at least one of query, customerEmail, or chatSessionId. A call with none of them is refused with 400 INVALID_INPUT. The filters combine with AND, and the response returns at most 100 rows in the same shape as the ticket list.
GET /mcp/tickets/{ticketId}/email-deliveries lists the outbound emails on one ticket, newest first, at most 100. Each row gives the recipient, the subject, the SendGrid delivery status, the delivered time, and the bounce reason. It covers the email threads that are linked to the ticket itself, such as fallback emails. Replies on an email-channel conversation are not linked to one ticket, so they are not listed. It reports only what SendGrid did with an email. A ticket that does not exist, or that you cannot see, returns 404 TICKET_NOT_FOUND. The ticket list field undeliveredReason gives the stored reason that the fallback path refused, or null.
Both routes are under /api/v1/mcp/ and they are also the MCP tools find_tickets and list_email_deliveries. They need the same Standard plan as the other inbox routes. A lower plan gets 402 PLAN_GATE_FAILED.
Changes
- 2026-10-20 — New route
POST /inbox/tickets/{ticketId}/attachmentsuploads a file for a reply from base64 or an https URL. - 2026-09-24 —
GET /inbox/ticketsacceptsview=undelivered. The view lists tickets whose reply could not reach the customer. Each ticket row has a newundeliveredReasonfield. It gives the stored reason that the fallback path refused, ornullwhen no refusal is stored. An undelivered ticket can also havenull: its fallback attempts ran out, or it has no fallback email thread. - 2026-09-24 — New route
GET /mcp/tickets/findfinds a ticket by text, customer email, or chat session. - 2026-09-24 — New route
GET /mcp/tickets/{ticketId}/email-deliverieslists the SendGrid outcome of each outbound email on a ticket.