/v1/admin/tickets/{ticket}/messagesAn answer to the trader, or with internal a note for the team. The same as
the reply box on the website, under the same rules.
An answer moves the ticket to awaiting_trader and restarts the clock on a
quiet ticket. The team's first answer switches the assistant off for good on
this ticket, and takes the ticket when nobody holds it yet: the first reply
takes it. The trader hears of it the way any reply tells them: the bell, a
toast, and an email when they are away and the firm has that mail switched on.
A note touches none of that. Not the status, not the assistant, not who holds the ticket, and the trader never sees it.
A picture travels as base64 inside JSON, like every upload in this API. Its type is worked out from the bytes, never from the name: a JPEG, PNG, GIF or WebP, at most 10 MB once decoded. It is stored re-encoded as WebP, at most 1600 pixels on its longest side.
Needs a firm. Our own staff name one with the Propper-Workspace header.
Request
| Field | Description |
|---|---|
body | The text, up to 5000 characters. Required without a picture |
image | A picture, as name (up to 255 characters, what the trader sees it called) and data (base64) |
internal | true for a note only the team reads. Defaults to false |
curl -X POST "https://api.proppertrading.com/v1/admin/tickets/tkt_cbqjrnz84cq45ca7/messages" \
-H "Authorization: Bearer ptat_9f2c1a44_3b8e7d2f5c9a1b4e6d8f0a2c4e6b8d1f" \
-H "Propper-Workspace: acme" \
-H "Idempotency-Key: 4e6a8c0b-2d4f-4b6a-8c1e-3f5a7b9d0c2e" \
-H "Content-Type: application/json" \
-d '{
"body": "Hi Leila, a larger account is a new challenge. This is where you find them.",
"image": { "name": "challenges.png", "data": "iVBORw0KGgoAAAANSUhEUgAAACgAAAAY..." }
}'
Response
201, with the message as the conversation
shows it:
{
"data": {
"object": "ticket_message",
"id": "tmsg_ua7ypkjx6kas5gvf",
"ticket_id": "tkt_cbqjrnz84cq45ca7",
"from": "admin",
"author": "Ios Tester",
"body": "Hi Leila, a larger account is a new challenge. This is where you find them.",
"image_url": "/v1/admin/tickets/tkt_cbqjrnz84cq45ca7/messages/tmsg_ua7ypkjx6kas5gvf/image",
"image_name": "challenges.png",
"internal": false,
"streaming": false,
"via_email": false,
"event": null,
"read_at": null,
"created_at": "2026-10-15T22:29:42+00:00"
},
"meta": {
"request_id": "req_01m50tyz4fewz8hhjt83b9p60g"
}
}
Retrying with the same Idempotency-Key answers the same message and sends
nothing. A second message is a new key.
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | workspace_required | Asked by our own staff without naming a firm |
| 400 | missing_idempotency_key | A POST without an Idempotency-Key header |
| 400 | invalid_image | image.data is not base64, is empty, or is not a JPEG, PNG, GIF or WebP |
| 400 | image_too_large | The picture is more than 10 MB once decoded |
| 401 | invalid_token | Not a token, or the secret does not match |
| 401 | revoked_token | Signed out here or from another device |
| 401 | expired_token | Ninety days without use, or a set expiry passed |
| 403 | admin_access_required | This account no longer helps run the environment |
| 403 | insufficient_scope | This device does not hold admin:write |
| 403 | workspace_not_permitted | The Propper-Workspace header names an environment this account cannot act in |
| 403 | forbidden | Without view_tickets and manage_tickets |
| 404 | resource_not_found | No ticket with that id in this firm |
| 409 | ticket_ended | The ticket was resolved or closed. A new question becomes a new ticket |
| 422 | validation_failed | No body and no picture, a body that is too long, or an image without name or data |