---
title: "Reply to a member"
method: POST
path: "/v1/support/conversations/{conversation}/messages"
tags: ["Support Inbox"]
---

# Reply to a member

`POST /v1/support/conversations/{conversation}/messages`

## Request

- Base URL: `https://api.subscriby.net`
- URL: `https://api.subscriby.net/v1/support/conversations/{conversation}/messages`
- Auth: HTTP bearer

```bash
curl -X POST https://api.subscriby.net/v1/support/conversations/$CONVERSATION_ID/messages \
  -H "Authorization: Bearer $SUBSCRIBY_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"body": "Sorry about that. Here is a fresh invite link, valid for 24 hours."}'
```

Answers `201` with the created message. `delivery_status` is `pending`: the reply has been recorded and queued, not yet delivered. Poll the message back or subscribe to `support.message.sent` rather than treating `201` as proof of receipt.

**Text only.** Sending media as a reply is a dashboard-inbox feature; this API accepts a `body` of up to 4000 characters, the message ceiling on the connectors.

Send `{"body": "...", "internal": true}` to record a private note for your team instead. Private notes are never delivered to the member and raise no webhook event.

Add `reply_to_message_id` to quote an earlier message, the way a chat client shows a threaded reply. It must name a message in **this** conversation; quoting across threads is refused, because it would put one member's words in front of another.

## Path parameters

- `conversation` string, uuid, required

## Headers

- `Idempotency-Key` string, uuid, required

## Request body

- object
  - `body` string, required: The reply text, up to 4,000 characters, the message ceiling on the connectors. Text only; media replies are a dashboard feature.
  - `internal` boolean: True records a private note for the team instead of a reply: never delivered to the member and raising no event. Defaults to false.
  - `reply_to_message_id` string, uuid: An earlier message of **this** conversation to quote, the way a chat client shows a threaded reply. A message from another thread is refused.

## Response `201`

The recorded message.

- object
  - `data` SupportMessageResource, required: One message in a support thread: who wrote it, through which surface, whether it reached the member, and its attachments without the connector's file ids.
    - `id` string, required: The message's id, the value `reply_to_message_id` quotes.
    - `conversation_id` string, required: The thread the message belongs to.
    - `project_id` string, required: The project the thread belongs to.
    - `direction` string, required: `inbound` (from the member) or `outbound` (from the team).
    - `author_kind` string, required: `contact` (the member), `creator` (a team member) or `system`.
    - `author_user_id` string, nullable, required: The team member who wrote an outbound reply; null for inbound and system messages.
    - `type` string, required: What the message carries: `text`, `photo`, `video`, `audio`, `voice`, `document`, `sticker`, `animation`, `location` or `contact`.
    - `source` string, required: The surface the message came through: `connector_chat` for a member writing to the connector's installation, `dashboard`, `api` or `mcp` for a reply from the team, `connector_relay_dm` or `connector_relay_group` for a reply the team typed on the platform itself, `ai_assistant` for a drafted reply. Messages recorded before the connector SDK carry the same meanings under the first connector's older words, its key followed by `_bot`, `_relay_dm` and `_relay_group`.
    - `delivery_status` string, required: On an outbound message, `pending` then `sent`, or `failed` / `unreachable`. **`unreachable` means the member has blocked the bot**: the reply is stored but will never arrive, and retrying will not help.
    - `body` string, nullable, required: The text, or the caption of a media message; null for media sent without a caption.
    - `internal` boolean, required: True for a private note the team wrote for itself, never delivered to the member. Withheld from tokens that cannot update the conversation.
    - `reply_to_message_id` string, nullable, required: The earlier message this one quotes; null when it quotes none.
    - `attachments` object[], required: The files on the message, described without the connector's file ids, which are credentials.
      - `kind` string, nullable, required: What the file is: `photo`, `video`, `audio`, `voice`, `document`, `sticker` or `animation`.
      - `mime` string, nullable, required: The MIME type, when the platform gave one.
      - `file_name` string, nullable, required: The original file name, when the platform gave one.
      - `size` integer, nullable, required: The size in bytes, when known.
      - `width` integer, nullable, required: The width in pixels for an image or video; null otherwise.
      - `height` integer, nullable, required: The height in pixels for an image or video; null otherwise.
      - `duration` integer, nullable, required: The length in seconds for audio and video; null otherwise.
      - `url` string, nullable, required: The stored copy's URL, populated only after the file has been fetched at least once; null until then.
    - `failure_reason` string, nullable, required: Why an outbound message `failed` or is `unreachable`; null otherwise.
    - `edited_at` string, nullable, required: When the member edited the message, ISO 8601; the stored message is updated in place and no event fires. Null when never edited.
    - `read_at` string, nullable, required: When the team read an inbound message, ISO 8601; null while unread.
    - `created_at` string, nullable, required: When the message was recorded, ISO 8601.

## Other responses

- `400`: `IDEMPOTENCY_KEY_MISSING`: every write needs an `Idempotency-Key` header. Send a fresh UUID per distinct operation.
- `401`: `AUTHENTICATION_REQUIRED`: the request carries no bearer token, or one that is revoked, malformed, or minted for another environment (an `sbt_test_` token on production).
- `403`: `TOKEN_MISSING_ABILITY`: the token is valid but does not carry the ability this endpoint requires; `error.context.required_ability` names the one to grant. An endpoint that also checks who owns a row or which tier the account is on answers `FORBIDDEN`, `TEAM_TIER_REQUIRED` or `CONNECTOR_TIER_REQUIRED` with the same status, and says so in its own description.
- `404`: `RESOURCE_NOT_FOUND`: an id in the path names nothing the token can see. `TENANT_MISMATCH`: the project sits outside the token's `scope:project:` allow-list, or the token carries no team scope. Both answer 404 rather than 403 so that existence outside the token's scope cannot be inferred.
- `409`: `IDEMPOTENCY_KEY_REUSED`: the key was already used in the last 24 hours with a different request body.
- `422`: `VALIDATION_FAILED`: the payload broke a rule, and `error.fields` maps each offending key to its messages. A refusal from the domain, such as a plan that cannot go on sale or a member who cannot be removed, uses the same code with `error.message` saying why and no `fields`. On this endpoint: `VALIDATION_FAILED`: when `body` is missing or over 4,000 characters, or `reply_to_message_id` is not a message of this conversation.
- `425`: `IDEMPOTENCY_REPLAY_IN_PROGRESS`: the first request with this key is still running; retry in a few seconds and the original response is replayed.
- `429`: `RATE_LIMITED`: the token has spent its 300 requests a minute or 10,000 an hour; `Retry-After` says when the next one is accepted.

---

[API](https://skmtc.dev/memberpass/apis/subscriby-rest-api.md) · [All operations](https://skmtc.dev/memberpass/apis/subscriby-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/memberpass/apis/subscriby-rest-api/revisions/59c4df91d764?raw)
