Reply to a buyer
POST/v1/threads/{id}/messages
scope messages.write
Every reply passes through the same chat-protection scan the seller dashboard applies. A message carrying contact details is refused with 422 blocked_by_chat_protection and a detected list saying what was found, or is masked before it is stored — check chat_protection.was_masked and show your user the body we return, not the one you sent.
Request body#
| Field | Type | Required | Description |
|---|---|---|---|
body | string | required | Your reply. Must be a non-empty string after trimming (body_required, 400) and at most 5000 characters (body_too_long, 400). |
Example
{
"body": "Yes, it is in stock — we ship within 24 hours."
}Response#
| Field | Type | Description |
|---|---|---|
data | object | The message as it was stored. |
data. | integer | The new message id. |
data. | integer | The conversation it landed in. |
data. | string | Always seller here. |
data. | string | What was actually stored — compare it with what you sent, because chat protection may have changed it. |
data. | string | ISO-8601 UTC. |
chat_protection | object | What our contact-detail scan did with your message. Your reply gets exactly the same treatment a seller typing in the dashboard gets. Absent when the scan did not run. |
chat_protection. | string | What it decided. A blocked message is refused outright with a 422, so a data object here means it was allowed. |
chat_protection. | boolean | true means the stored text DIFFERS from what you sent — a phone number or email was removed. Worth logging: your system and the buyer are now looking at different text. |
Example
{
"data": {
"id": 174421,
"thread_id": 107373,
"from": "seller",
"body": "Yes, it is in stock — we ship within 24 hours.",
"created_at": "2026-09-10T06:15:30Z"
},
"chat_protection": {
"decision": "allow",
"was_masked": false
}
}