Skip to main content

Peep OA — Public API Documentation

Overview

Public API for external (3rd-party) integration with the Peep Official Account system to send messages to users via OA.

Base URL:

https://oa-api.peepshare.ai

Postman Collection: Download — Import into Postman to test the API instantly


Table of Contents


Authentication

The Public API uses API Key + JWT Token authentication.

Steps:

  1. Obtain api_key and api_secret from the admin team
  2. Call POST /api/v2/public/auth/token to receive access_token and refresh_token
  3. Include access_token in the header of every request:
    Authorization: Bearer <access_token>
  4. When the token expires, use POST /api/v2/public/auth/refresh-token to obtain a new token

Rate Limiting

Endpoint GroupLimit
All endpoints200 requests / min
/api/v2/public/upload20 requests / min

When the rate limit is exceeded, you will receive HTTP 429 Too Many Requests:

{
"statusCode": 429,
"data": "ThrottlerException: Too Many Requests",
"timestamp": "2026-07-22 17:23:30",
"path": "/api/v2/public/webhook/broadcast-greeting"
}

Response Format

Success Response

All successful responses are wrapped with a data field:

{
"data": { ... }
}

Error Response

When an error occurs, the response has the following format:

{
"statusCode": 404,
"data": {
"statusCode": 404,
"code": "NOT_FOUND",
"info": "not found account_provider in db"
},
"timestamp": "2026-07-22 15:46:10",
"path": "/api/v2/public/webhook/broadcast-greeting"
}
FieldTypeDescription
statusCodenumberHTTP status code
data.codestringError code (see table below)
data.infostringError details
timestampstringTime when the error occurred
pathstringEndpoint that was called

Error Codes:

CodeHTTP StatusDescription
UNAUTHORIZED401Token is invalid or expired
NOT_FOUND404Requested resource not found
BAD_REQUEST400Invalid request data
JOB_BUSY400A broadcast job is currently in progress
INTERNAL_SERVER_ERROR500Internal server error

Endpoints


1. Generate Token

Generate an access token for API calls.

POST /api/v2/public/auth/token

Authentication: Not required (uses api_key + api_secret instead)

Request Body:

FieldTypeRequiredDescription
api_keystringYour API Key
api_secretstringYour API Secret

Example Request:

curl -X POST https://<domain>/api/v2/public/auth/token \
-H "Content-Type: application/json" \
-d '{
"api_key": "pk_abc123...",
"api_secret": "sk_xyz789..."
}'

Example Response:

{
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": "1h"
}
}

2. Refresh Token

Request a new access token when the current one has expired.

POST /api/v2/public/auth/refresh-token

Authentication: Not required

Request Body:

FieldTypeRequiredDescription
refresh_tokenstringRefresh token from Generate Token

Example Request:

curl -X POST https://<domain>/api/v2/public/auth/refresh-token \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "eyJhbGciOiJIUzI1NiIs..."
}'

Example Response:

{
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": "1h"
}
}

3. Broadcast Greeting

Send a greeting message (pre-configured welcome message in OA) to a user.

POST /api/v2/public/webhook/broadcast-greeting

Authentication: Bearer Token

Request Body:

FieldTypeRequiredDescription
user_idstringRecipient's User ID

Example Request:

curl -X POST https://<domain>/api/v2/public/webhook/broadcast-greeting \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"user_id": "user_123456"
}'

Example Response:

{
"data": {
"id": "uuid-of-greeting",
"title": "Welcome Message",
"slug": "welcome-message",
"data": [
{
"type": "message",
"msg": "Hello! Welcome!"
}
]
}
}

Send an image to a user. The link field is optional — if provided, tapping the image opens the URL; if omitted, it's a plain image.

POST /api/v2/public/webhook/broadcast-image-link

Authentication: Bearer Token

Request Body:

FieldTypeRequiredDescription
user_idstringRecipient's User ID
image_urlstringImage URL
linkstringURL embedded in image (if set, tapping image opens this URL)

Example Request:

curl -X POST https://<domain>/api/v2/public/webhook/broadcast-image-link \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"user_id": "user_123456",
"image_url": "https://images.unsplash.com/photo-1564760290292-23341e4df6ec?q=80&w=1740&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"link": "https://example.com/landing-page"
}'

Example Response:

{
"data": "success"
}

5. Broadcast Message (Single User)

Send a text message to a single user.

POST /api/v2/public/webhook/broadcast-message

Authentication: Bearer Token

Request Body:

FieldTypeRequiredDescription
user_idstringRecipient's User ID
msgstringMain message content
descstringShort text shown on push notification

Example Request:

curl -X POST https://<domain>/api/v2/public/webhook/broadcast-message \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"user_id": "user_123456",
"msg": "Hello! You have a new message",
"desc": "Notification"
}'

Example Response:

{
"data": "success"
}

6. Broadcast Message (Mass)

Broadcast messages to multiple users or all users.

POST /api/v2/public/message/broadcast-message

Authentication: Bearer Token

Request Body:

FieldTypeRequiredDescription
targetstringSend type: all, selected, single
user_idsstring[]❌*List of User IDs (*required when target = selected or single)
itemsarrayMessage items (max 5 items)

Item Object:

FieldTypeRequiredDescription
typestringMessage type (see Message Item Types)
msgstring | string[]Message content
descstringDescription (max 170 characters)
linkstringURL link (for type = oa_image_link)
audio_uuidstring❌*UUID from POST /upload/audio (*required when type = audio)
audio_sizenumber❌*Audio file size in bytes (*required when type = audio)
audio_durationnumber❌*Audio duration in seconds (*required when type = audio)

Example Request — type message (text message):

curl -X POST https://<domain>/api/v2/public/message/broadcast-message \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"target": "selected",
"user_ids": ["user_001", "user_002", "user_003"],
"items": [
{
"type": "message",
"msg": "Hello! This is a broadcast message",
"desc": "Short text on push notification"
}
]
}'

Example Request — type oa_image_link (image with link):

curl -X POST https://<domain>/api/v2/public/message/broadcast-message \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"target": "all",
"items": [
{
"type": "oa_image_link",
"msg": "https://images.unsplash.com/photo-1564760290292-23341e4df6ec?q=80&w=1740&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"link": "https://example.com/promo",
"desc": "Special promotion today"
}
]
}'

Example Request — type image (plain image, no link):

curl -X POST https://<domain>/api/v2/public/message/broadcast-message \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"target": "single",
"user_ids": ["user_001"],
"items": [
{
"type": "image",
"msg": "https://images.unsplash.com/photo-1564760290292-23341e4df6ec?q=80&w=1740&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
}
]
}'

Example Request — type flex (flex message):

curl -X POST https://<domain>/api/v2/public/message/broadcast-message \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"target": "selected",
"user_ids": ["user_001"],
"items": [
{
"type": "flex",
"msg": "{\"type\":\"bubble\",\"hero\":{\"type\":\"image\",\"url\":\"https://developers-resource.landpress.line.me/fx/img/01_1_cafe.png\",\"size\":\"full\",\"aspectRatio\":\"20:13\",\"aspectMode\":\"cover\",\"action\":{\"type\":\"uri\",\"uri\":\"https://line.me/\"}},\"body\":{\"type\":\"box\",\"layout\":\"vertical\",\"contents\":[{\"type\":\"text\",\"text\":\"Brown Cafe\",\"weight\":\"bold\",\"size\":\"xl\"},{\"type\":\"box\",\"layout\":\"vertical\",\"margin\":\"lg\",\"spacing\":\"sm\",\"contents\":[{\"type\":\"box\",\"layout\":\"baseline\",\"spacing\":\"sm\",\"contents\":[{\"type\":\"text\",\"text\":\"Place\",\"color\":\"#aaaaaa\",\"size\":\"sm\",\"flex\":1},{\"type\":\"text\",\"text\":\"Flex Tower, 7-7-4 Midori-ku, Tokyo\",\"wrap\":true,\"color\":\"#666666\",\"size\":\"sm\",\"flex\":5}]},{\"type\":\"box\",\"layout\":\"baseline\",\"spacing\":\"sm\",\"contents\":[{\"type\":\"text\",\"text\":\"Time\",\"color\":\"#aaaaaa\",\"size\":\"sm\",\"flex\":1},{\"type\":\"text\",\"text\":\"10:00 - 23:00\",\"wrap\":true,\"color\":\"#666666\",\"size\":\"sm\",\"flex\":5}]}]}]},\"footer\":{\"type\":\"box\",\"layout\":\"vertical\",\"spacing\":\"sm\",\"contents\":[{\"type\":\"button\",\"style\":\"link\",\"height\":\"sm\",\"action\":{\"type\":\"uri\",\"label\":\"CALL\",\"uri\":\"https://line.me/\"}},{\"type\":\"button\",\"style\":\"link\",\"height\":\"sm\",\"action\":{\"type\":\"uri\",\"label\":\"WEBSITE\",\"uri\":\"https://line.me/\"}},{\"type\":\"box\",\"layout\":\"vertical\",\"contents\":[],\"margin\":\"sm\"}],\"flex\":0}}",
"desc": "Brown Cafe"
}
]
}'

Example Request — type video:

Use the ticket_id returned by Upload Video as msg.

curl -X POST https://<domain>/api/v2/public/message/broadcast-message \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"target": "all",
"items": [
{
"type": "video",
"msg": "0e45c26c-cff3-45cb-b51d-876d55d40bea",
"desc": "New product clip"
}
]
}'

⚠️ Transcoding must be finished before you broadcast the ticket. If the ticket is still converting the broadcast job fails when it runs.

Example Request — type audio:

Use the values returned by Upload Audio: audio_url goes in msg, the other three go in the same item.

curl -X POST https://<domain>/api/v2/public/message/broadcast-message \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"target": "selected",
"user_ids": ["user_001"],
"items": [
{
"type": "audio",
"msg": "https://example.cos.ap-bangkok.myqcloud.com/uploads/public/my-oa-slug/audios/2026/08/31/audio-1756612345678-123456789/a1b2c3d4e5f6.mp3",
"audio_uuid": "7f3a19b2-8c41-4d2e-9a05-1b6e4c7d8f90",
"audio_size": 241206,
"audio_duration": 15,
"desc": "Voice announcement"
}
]
}'

Example Response:

{
"data": {
"id": "broadcast-uuid",
"data": [
{
"type": "message",
"msg": "Hello! This is a broadcast message"
}
],
"broadcast_at": "2025-07-22 10:30:00",
"created_at": "2025-07-22 10:30:00",
"estimate_time_detail": {
"total_job_time_minutes": 2
}
}
}

7. Broadcast Group Message

Broadcast messages to all members in a group.

POST /api/v2/public/message/broadcast-group-message

Authentication: Bearer Token

Request Body:

FieldTypeRequiredDescription
account_provider_group_idstringUUID of the target group
itemsarrayMessage items (max 5 items, same format as Item Object)

Item types video and audio are supported here too, with the same fields as in Broadcast Message (Mass).

Example Request:

curl -X POST https://<domain>/api/v2/public/message/broadcast-group-message \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"account_provider_group_id": "group-uuid-abc123",
"items": [
{
"type": "message",
"msg": "Message to all group members"
},
{
"type": "oa_image_link",
"msg": "https://example.com/event.jpg",
"link": "https://example.com/event",
"desc": "Special Event"
}
]
}'

Example Response:

{
"data": {
"id": "broadcast-uuid",
"data": [...],
"broadcast_at": "2025-07-22 10:30:00",
"created_at": "2025-07-22 10:30:00",
"estimate_time_detail": {
"total_job_time_minutes": 1
}
}
}

8. Broadcast Member Session

Broadcast messages to members in a member group broadcast session.

POST /api/v2/public/message/broadcast-member-session

Authentication: Bearer Token

Request Body:

FieldTypeRequiredDescription
member_group_broadcast_idstringUUID of the member group broadcast
itemsarrayMessage items (max 5 items, same format as Item Object)

Item types video and audio are supported here too, with the same fields as in Broadcast Message (Mass).

Example Request:

curl -X POST https://<domain>/api/v2/public/message/broadcast-member-session \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"member_group_broadcast_id": "mgb-uuid-xyz789",
"items": [
{
"type": "message",
"msg": "Message for member session"
}
]
}'

Example Response:

{
"data": {
"id": "broadcast-uuid",
"data": [...],
"broadcast_at": "2025-07-22 10:30:00",
"created_at": "2025-07-22 10:30:00",
"estimate_time_detail": {
"total_job_time_minutes": 3
}
}
}

Upload

Endpoints for uploading media files. Use the returned URL / ticket with the broadcast endpoints below.

  • Content type is multipart/form-data
  • Requires the same Authorization: Bearer <access_token> header as every other endpoint
  • Files are stored under the account provider bound to your token, and every upload is logged with the API key, provider slug, service and IP that made it

⚠️ Uploaded images are served as public static files — anyone who knows the URL can open them (filenames are randomised). Do not upload confidential material.


9. Upload Image

Upload an image and get a url to use with Broadcast Image Link or with message items of type image / oa_image_link.

POST /api/v2/public/upload/image

Authentication: Bearer Token Content-Type: multipart/form-data

Form Data:

FieldTypeRequiredDescription
filefileJPG / JPEG / PNG / WEBP — max 5 MB

Example Request:

curl -X POST https://<domain>/api/v2/public/upload/image \
-H "Authorization: Bearer <access_token>" \
-F "file=@/path/to/image.jpg"

Example Response:

{
"data": {
"src_file": "uploads/public/my-oa-slug/images/2026/08/31/a1b2c3d4e5f6.jpg",
"url": "https://oa-api.peepshare.ai/uploads/public/my-oa-slug/images/2026/08/31/a1b2c3d4e5f6.jpg"
}
}

10. Upload Video

Upload a video. The system creates a transcode job and returns a ticket_id for Broadcast Video.

POST /api/v2/public/upload/video

Authentication: Bearer Token Content-Type: multipart/form-data

Form Data:

FieldTypeRequiredDescription
videofileMP4 / AVI / MKV / MOV / WEBM / OGG / 3GP / 3G2 — max 100 MB

Example Request:

curl -X POST https://<domain>/api/v2/public/upload/video \
-H "Authorization: Bearer <access_token>" \
-F "video=@/path/to/clip.mp4"

Example Response:

{
"data": {
"ticket_id": "0e45c26c-cff3-45cb-b51d-876d55d40bea"
}
}

Transcoding runs asynchronously. Wait until it finishes before calling broadcast-video with this ticket_id.


11. Upload Audio

Upload an audio file to Tencent COS and get every value required by Broadcast Audio.

POST /api/v2/public/upload/audio

Authentication: Bearer Token Content-Type: multipart/form-data

Form Data:

FieldTypeRequiredDescription
audiofileMP3 / WAV / OGG / M4A / AAC — max 100 MB

Example Request:

curl -X POST https://<domain>/api/v2/public/upload/audio \
-H "Authorization: Bearer <access_token>" \
-F "audio=@/path/to/voice.mp3"

Example Response:

{
"data": {
"audio_url": "https://example.cos.ap-bangkok.myqcloud.com/uploads/public/my-oa-slug/audios/2026/08/31/audio-1756612345678-123456789/a1b2c3d4e5f6.mp3",
"audio_uuid": "7f3a19b2-8c41-4d2e-9a05-1b6e4c7d8f90",
"audio_size": 241206,
"audio_duration": 15
}
}

Pass all four values (audio_url, audio_uuid, audio_size, audio_duration) straight through to broadcast-audio.

Error Response (unsupported file):

{
"statusCode": 400,
"code": "BAD_REQUEST",
"info": "Invalid audio file type: application/pdf"
}

12. Broadcast Video

Send a video to a single user, using a ticket_id from Upload Video.

POST /api/v2/public/webhook/broadcast-video

Authentication: Bearer Token

Request Body:

FieldTypeRequiredDescription
user_idstringRecipient's User ID
ticket_idstringTicket returned by POST /upload/video

Example Request:

curl -X POST https://<domain>/api/v2/public/webhook/broadcast-video \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"user_id": "user_123456",
"ticket_id": "0e45c26c-cff3-45cb-b51d-876d55d40bea"
}'

Example Response:

{
"data": "success"
}

Error Response (still transcoding):

{
"statusCode": 400,
"code": "BAD_REQUEST",
"info": "video is process convert status"
}

13. Broadcast Audio

Send an audio file to a single user, using the values from Upload Audio.

POST /api/v2/public/webhook/broadcast-audio

Authentication: Bearer Token

Request Body:

FieldTypeRequiredDescription
user_idstringRecipient's User ID
audio_urlstringURL returned by POST /upload/audio
audio_uuidstringUUID returned by the upload
audio_sizenumberFile size in bytes
audio_durationnumberDuration in seconds

Example Request:

curl -X POST https://<domain>/api/v2/public/webhook/broadcast-audio \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"user_id": "user_123456",
"audio_url": "https://example.cos.ap-bangkok.myqcloud.com/uploads/public/my-oa-slug/audios/2026/08/31/audio-1756612345678-123456789/a1b2c3d4e5f6.mp3",
"audio_uuid": "7f3a19b2-8c41-4d2e-9a05-1b6e4c7d8f90",
"audio_size": 241206,
"audio_duration": 15
}'

Example Response:

{
"data": "success"
}

Message Item Types

TypeDescriptionmsg FormatRequires link?
messagePlain text messagestring
oa_image_linkImage that opens a link on tapstring (image URL)
imagePlain image (no link)string (image URL)
flexFlex messagestring (JSON format)
videoVideo messagestring (ticket_id from Upload Video)
audioAudio messagestring (audio_url from Upload Audio)
  • audio items also require audio_uuid, audio_size and audio_duration in the same item — pass through the values returned by POST /upload/audio.
  • video items only carry the ticket_id; the transcode job for that ticket must be finished before broadcasting.
  • video and audio are available on broadcast-message, broadcast-group-message and broadcast-member-session.

Webhook Events

When Peep OA receives an event from the client, it forwards the event to the webhook_url configured for the OA account. The request body contains the event payload described below.

Message Event

The message event is sent when a client sends a message to the OA account.

Example Payload:

{
"event_type": "message",
"username": "example-oa",
"data": {
"MsgVersion": 0,
"MsgBody": [{}],
"CallbackCommand": "C2C.CallbackAfterSendMsg",
"InstanceId": 108534120,
"From_Account": "example-user",
"To_Account": "example-oa",
"MsgRandom": 3243647983,
"MsgSeq": 2384864640,
"MsgTime": 1787725633,
"SupportMessageExtension": 0,
"MsgKey": "2384864640_3243647983_1787725633",
"MsgId": "144115266418381458-1787725634-3243647983",
"OnlineOnlyFlag": 0,
"SendMsgResult": 0,
"ErrorInfo": "send msg succeed",
"UnreadMsgNum": 4,
"EventTime": 1787725633786,
"OfflinePushInfo": {
"PushFlag": 0,
"Desc": "hi",
"Ext": "{\"userId\":\"example-user\",\"groupId\":null,\"senderName\":\"Example User\",\"Title\":\"Example User\"}"
}
},
"timestamp": "2026-08-26T06:27:15.227Z"
}
FieldTypeDescription
event_typestringEvent type. message indicates a message event
usernamestringUsername of the OA account receiving the event
dataobjectOriginal message event data received from the client
timestampstringTime the webhook payload was created, in ISO 8601 UTC

The fields inside data come from the client message event and may vary by message type. Consumers should ignore unknown fields and handle optional fields safely.

Message Body Types

The data.MsgBody array contains one or more message elements. Each element has a MsgType and a corresponding MsgContent object.

Image (TIMImageElem)

ImageInfoArray contains the available image variants. Type identifies the variant returned by the client (for example, original, large, or thumbnail).

[
{
"MsgType": "TIMImageElem",
"MsgContent": {
"UUID": "20008744_144115266418381458_4f6b4d0a394f144dc3879d41fa6df54b.heic",
"ImageFormat": 255,
"ImageInfoArray": [
{
"Type": 1,
"Size": 991921,
"Width": 2268,
"Height": 4032,
"URL": "https://sgp.rich.my-imcloud.com/download/4f6b4d0a394f144dc3879d41fa6df54b.heic?<signed-query>"
},
{
"Type": 2,
"Size": 0,
"Width": 720,
"Height": 1280,
"URL": "https://sgp.rich.my-imcloud.com/download/4f6b4d0a394f144dc3879d41fa6df54b.heic?<signed-query>"
},
{
"Type": 3,
"Size": 0,
"Width": 198,
"Height": 352,
"URL": "https://sgp.rich.my-imcloud.com/download/4f6b4d0a394f144dc3879d41fa6df54b.heic?<signed-query>"
}
]
}
}
]
Video (TIMVideoFileElem)
[
{
"MsgType": "TIMVideoFileElem",
"MsgContent": {
"VideoSize": 13268689,
"VideoSecond": 20,
"VideoFormat": "",
"ThumbSize": 86043,
"ThumbWidth": 360,
"ThumbHeight": 640,
"ThumbFormat": "",
"VideoDownloadFlag": 2,
"VideoUUID": "20008744_144115266418381458_23a1703fdea76870a2bafc8cd3cc8459.mp4",
"VideoUrl": "https://sgp.rich.my-imcloud.com/download/23a1703fdea76870a2bafc8cd3cc8459.mp4?<signed-query>",
"ThumbDownloadFlag": 2,
"ThumbUUID": "20008744_144115266418381458_1793dac254d4219958da85e26f6f90ad.jpg",
"ThumbUrl": "https://sgp.rich.my-imcloud.com/download/1793dac254d4219958da85e26f6f90ad.jpg?<signed-query>"
}
}
]
Audio (TIMSoundElem)
[
{
"MsgType": "TIMSoundElem",
"MsgContent": {
"Size": 108883,
"Second": 3,
"Download_Flag": 2,
"UUID": "20008744_144115266418381458_af2c32622a92b0ce83e916b43c0de65f.m4a",
"Url": "https://sgp.rich.my-imcloud.com/download/af2c32622a92b0ce83e916b43c0de65f.m4a?<signed-query>"
}
}
]

Download URLs contain temporary signed query parameters. Consumers should use the complete URL received in the webhook payload and should not persist or hard-code the example URLs.


Quick Start

# 1. Get token
TOKEN=$(curl -s -X POST https://<domain>/api/v2/public/auth/token \
-H "Content-Type: application/json" \
-d '{"api_key":"pk_...","api_secret":"sk_..."}' | jq -r '.data.access_token')

# 2. Send message to a user
curl -X POST https://<domain>/api/v2/public/webhook/broadcast-message \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"user_id": "target_user_id",
"msg": "Hello from API!"
}'

# 3. Broadcast to all users
curl -X POST https://<domain>/api/v2/public/message/broadcast-message \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"target": "all",
"items": [{"type": "message", "msg": "📢 Important announcement!"}]
}'