Before you start
Get your credentials
You need a SERVER API key and your account id (
x-account-id). Create keys in the dashboard under Settings → API keys. Never expose SERVER keys in browser code.Quick reference — all ticket & communication endpoints
| Method | Path | What it does |
|---|---|---|
GET | /ticket-pipelines | List pipelines and ordered stages, including internal open/closed state |
POST | /tickets | Create a ticket (manual, from review, with optional assignees) |
GET | /tickets/{id} | Get one ticket including communication_summary and assignees |
PATCH | /tickets/{id} | Update title, priority, stage, assignees in one call |
GET | /tickets/{id}/assignees | List active assignee records |
POST | /tickets/{id}/assignees | Add one assignee or watcher |
DELETE | /tickets/{id}/assignees | Remove an assignee |
GET | /communication/threads | List thread shells (summaries, no message bodies) |
POST | /communication/threads | Attach a new thread to a ticket (or review/customer) |
GET | /communication/threads/{id} | Get one thread shell |
GET | /communication/threads/{id}/messages | List messages in chronological order |
POST | /communication/threads/{id}/messages | Add inbound, outbound, or internal message |
Part 1 — Creating tickets
1A. Find the pipeline and stage ids
id and stage code. If both are omitted when creating a ticket, Masivo uses the account’s default pipeline and that pipeline’s default stage.
Pipeline administration is dashboard-only under CRM → Pipelines. The
Storefront API intentionally exposes discovery through GET /ticket-pipelines
but does not expose create, update, or delete pipeline endpoints.
1B. Create a manual ticket
Use when an agent or your system opens a case without a linked review.| Field | Required? | Notes |
|---|---|---|
title | Yes | Max 200 characters |
description | No | Max 5000 characters |
pipeline_id | No | Pipeline UUID from GET /ticket-pipelines; defaults to the account pipeline |
stage_code | No | Internal stage code in that pipeline; defaults to its default stage |
priority | No | low, medium, high, urgent — default medium |
source_type | No | Default manual |
review_id | No | If set, creates a review ticket instead (see below) |
customer_id | No | External customer id; stored as a customer association |
assignee_user_ids | No | Array of team member UUIDs |
actor_user_id | No | Who performed the action; defaults to a system actor |
idempotency_key | No | Strongly recommended. Re-posting the same key returns the original ticket |
required_fields | No | Required when creating directly in resolved / closed stage |
idempotency_key from your integration so network retries do not create duplicate tickets.
1C. Create a ticket from a review
Whenreview_id is present, Masivo:
- Creates a ticket with
source_type: review - Deduplicates — only one open ticket per review
- Links review, customer, and journey associations when available
- Creates a review communication thread with the review answers as the first inbound message
review channel thread with the submission text.
Part 2 — Reading and updating tickets
2A. Get a ticket (with communication rollup)
communication_summary on list/board views. Load full threads only when the user opens the conversation.
2B. Update title, description, or priority
2C. Change stage (move on the board)
GET /ticket-pipelines; do not hard-code display names.
To move between pipelines, send both target values:
reopened_at when leaving a closed-type stage. An inbound customer reply also reopens the ticket automatically in the first open stage of its current pipeline.
You can combine stage changes with assignee updates in the same PATCH request.
2D. Manage assignees
Option A — via PATCH (common in integrations):Part 3 — Communication threads (the conversation layer)
Mental model
- One ticket can have many threads (e.g. email + WhatsApp).
- Each thread belongs to one channel and optionally maps to an external provider thread id.
- Messages always belong to exactly one thread.
3A. List threads for a ticket
Returns summaries only — not message bodies.| Query param | Default | Purpose |
|---|---|---|
ticket_id | — | Filter threads on this ticket |
review_id | — | Filter by review |
customer_id | — | Filter by customer external id |
channel | — | email, whatsapp, chat, review, system, other |
status | — | open or closed |
from / to | 0 / 49 | Pagination (max 100 rows per request) |
3B. Create a thread on a ticket
Do this once per external conversation before posting messages.ticket_id, review_id, customer_id, or external_thread_id.
If you POST again with the same (channel, channel_account_id, external_thread_id), Masivo returns the existing thread — this is intentional.
3C. List messages in a thread
sent_at, then created_at). Pagination default: rows 0–99, max range 200.
3D. Post messages
Inbound customer email (first message in thread)
Agent outbound reply (linked to parent)
message_type is omitted and parent_message_id is set, Masivo defaults to reply.
Internal note (agents only — customer never sees this)
TICKET_CUSTOMER_REPLIED or TICKET_AGENT_REPLIED.
Message field cheat sheet
| Field | When to use |
|---|---|
direction | Required. inbound = customer→you, outbound = you→customer, internal = team-only |
external_message_id | Required for sync. Provider’s unique message id |
parent_message_id | When replying to a specific message in the same thread |
body_text / body_html / subject | At least one required |
sender_user_id | Agent UUID for outbound/internal |
sender_email / sender_display_name | Customer identity for inbound email |
read_at | Set on inbound when your UI marks as read (stops unread increment) |
raw_payload | Store provider metadata for debugging |
What updates automatically when you post a message
Masivo updates the parent thread:latest_message_id,latest_message_at,latest_message_previewlatest_message_channel,latest_message_directionunread_count(+1 for inbound messages withoutread_at)
communication_summary updates the next time you GET /tickets/{id}.
Part 4 — End-to-end recipes
Recipe A — Angry email, then resolve on WhatsApp
Real pattern from multi-channel support:
Verify:
Recipe B — Sync-only integration (your app owns the UI)
- Webhook from email provider → map to
POST …/threads(if new) +POST …/messages. - Poll
GET …/threads?ticket_id=for unread badges. - When agent sends from your UI →
POST …/messageswithdirection: outbound. - Stage changes →
PATCH /tickets/{id}.
thread.id and message.id alongside provider ids in your database.
Recipe C — Review → ticket → human follow-up
- Customer submits review (existing review API).
POST /ticketswithreview_id— ticket + review thread created automatically.- Agent reads review in Conversation tab or via messages API.
- Create additional threads (email/WhatsApp) if you contact the customer on other channels.
- Resolve with
resolution_summarywhen done.
Part 5 — Working in the dashboard
| Area | What you can do |
|---|---|
| Tickets board | Kanban by stage; cards show communication preview and unread count |
| Ticket detail — Summary | Stats, profile header, linked channels |
| Ticket detail — Conversation | Full timeline across all threads; filter by channel/direction; search |
| Ticket detail — Activity | Stage change history with who changed it and when |
| Ticket detail — Sidebar | ID, status, stage, owner, assignees, ecommerce refs, resolution fields |
| Tabs | Resolution, associations, ticket info, metadata JSON |
Part 6 — Pagination and limits
| Endpoint | Default range | Max range |
|---|---|---|
GET /communication/threads | from=0, to=49 | 100 rows |
GET /communication/threads/{id}/messages | from=0, to=99 | 200 rows |
from/to as inclusive row indices (not page numbers). Example: next page of threads → from=50&to=99.
Part 7 — Errors and how to fix them
| HTTP | Typical cause | Fix |
|---|---|---|
400 | Business rule failed | Read details in body — e.g. missing resolution_summary, invalid parent message |
404 | Wrong id or wrong account | Check x-account-id and UUID |
422 | Validation failed | Fix field types/enums; ensure from <= to and range limits |
400 messages:
| Message | Meaning |
|---|---|
Parent message not found in this thread | parent_message_id is wrong or belongs to another thread |
Thread requires a ticket, review, customer, or external id | Create-thread body missing context fields |
subject, body_text, or body_html is required | Empty message body |
| Stage required field errors | Pass required_fields when moving to resolved or closed |
Part 8 — Automation hooks
Communication on ticket threads emits journey events you can use in Ticket Journey Triggers:| Event | Use it to… |
|---|---|
TICKET_CUSTOMER_REPLIED | Escalate priority, notify assignee, start SLA timer |
TICKET_AGENT_REPLIED | Move to waiting_customer, send survey |
TICKET_MESSAGE_RECEIVED | Any inbound on ticket — broader than customer-only |
TICKET_THREAD_ATTACHED | Welcome workflow when first channel linked |
TICKET_STAGE_CHANGED, TICKET_RESOLVED, etc.) work the same way.
Frequently asked questions
What is the difference between a ticket and a thread?
What is the difference between a ticket and a thread?
The ticket is the case you track on the board (stage, priority, assignees, resolution). A thread is one conversation pipe on that case — usually one email chain or one WhatsApp chat. One ticket, many threads.
Why does GET /threads not return message text?
Why does GET /threads not return message text?
Performance. Thread list endpoints return shells with
latest_message_preview only. Call GET /communication/threads/{id}/messages for full history.Do I need external_thread_id and external_message_id?
Do I need external_thread_id and external_message_id?
If you integrate with an external inbox: yes. They prevent duplicates when webhooks retry. If you only use the Masivo dashboard manually, Masivo still generates internal UUIDs — but integrations should always send provider ids.
Can one message belong to two threads?
Can one message belong to two threads?
No. Each message belongs to exactly one thread. To link conversations across channels, attach multiple threads to the same ticket.
How does unread_count work?
How does unread_count work?
Each inbound message without
read_at increments the thread’s unread_count. The ticket communication_summary.unread_count sums all threads on that ticket. Mark as read by posting/updating with read_at set (future API) or via dashboard consumption.What happens if I create two tickets for the same review?
What happens if I create two tickets for the same review?
Masivo deduplicates review tickets — if an open ticket already exists for that
review_id, the API returns the existing ticket instead of creating a second one.Can I delete a thread or message via API?
Can I delete a thread or message via API?
Public Storefront endpoints documented here are create/list/get only. Soft-delete exists at the database layer for admin operations but is not exposed on these routes.
Which channel value should I use?
Which channel value should I use?
Use the channel that matches how the customer contacted you:
email, whatsapp, chat, review (auto for review submissions), system, or other. The channel on a message defaults to its thread’s channel if omitted.Checklist before going to production
- SERVER API key stored securely on your backend
- Every create-ticket call sends
idempotency_key - Every synced message sends
external_message_id - Every synced thread sends
external_thread_id+channel_account_id - Resolve/close flows collect
resolution_summary/closure_reason - Your UI loads thread shells first, messages on demand
- Journey automations tested for
TICKET_CUSTOMER_REPLIEDand stage changes
Related documentation
- Tickets & communication (concepts) — diagrams and field definitions
- Ticket Journey Triggers — automate on ticket events
- Create and manage review forms — review → ticket pipeline
- API reference — Tickets group in the sidebar