> ## Documentation Index
> Fetch the complete documentation index at: https://opentrain.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Send Message

> Send a plain-text message into an existing conversation you participate in.

Sends a plain-text message into an existing conversation you participate in. The same membership, rate-limit, and content-policy checks as the in-app messaging flow apply. This endpoint never creates conversations — get a `conversationId` from [`GET /messages`](/docs/developers/api-reference/messages/list) or by [starting a proposal thread](/docs/developers/api-reference/proposals/conversation).

**Requirements:** `messages:write` scope + the `public_api_messaging_writes` feature + a **claimed** account (unclaimed accounts get `403` with `details.reason: "account_claim_required"` and a `claimUrl`).

## Request

<ParamField body="conversationId" type="string" required>
  Existing conversation you participate in.
</ParamField>

<ParamField body="content" type="string" required>
  Plain-text message, 1–10,000 characters.
</ParamField>

## Response

Returns `201` on success.

<ResponseField name="ok" type="boolean">
  `true` on success.
</ResponseField>

<ResponseField name="message" type="object">
  The created message, in the same shape as the message entries on [`GET /messages`](/docs/developers/api-reference/messages/list): `{id, createdAt, updatedAt, conversationId, jobId, senderUserId, isFromViewer, content, messageType, system, hasUnreadFlag, attachmentCount, hasAudio, editedAt, reactionCount}`.
</ResponseField>

## Errors

| Status | `code`         | Meaning                                                                                                                                                                                                                                                                        |
| ------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `400`  | `BAD_REQUEST`  | Empty body, invalid JSON, or invalid fields (`details.issues` carries zod details — e.g. `content` empty or over 10,000 chars)                                                                                                                                                 |
| `401`  | `UNAUTHORIZED` | Missing or invalid token                                                                                                                                                                                                                                                       |
| `403`  | `FORBIDDEN`    | Missing `messages:write` scope, feature disabled, account not claimed (`details.reason: "account_claim_required"`, `details.claimUrl`), not a participant in the conversation, or content blocked by policy (`details.reason: "message_policy_blocked"` with `details.policy`) |
| `404`  | `NOT_FOUND`    | No such conversation                                                                                                                                                                                                                                                           |
| `409`  | `CONFLICT`     | Send blocked — `details.reason` is `employer_first_message_required` (candidate replying before the employer's first message), `read_only_conversation`, or `message_blocked`                                                                                                  |
| `429`  | `RATE_LIMITED` | Messaging rate limit hit — retry after `details.retryAfterSeconds`                                                                                                                                                                                                             |

<RequestExample>
  ```bash curl theme={null}
  curl -sS -X POST https://app.opentrain.ai/api/public/v1/messages \
    -H "Authorization: Bearer $OT_API_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "conversationId": "<CONVERSATION_ID>",
      "content": "Hi Maria — your interview looked great. Could you start with a 5,000-post batch next week?"
    }'
  ```

  ```bash CLI theme={null}
  opentrain messages send --conversation-id <CONVERSATION_ID> \
    --content "Hi Maria — your interview looked great. Could you start with a 5,000-post batch next week?" --json
  ```

  ```json MCP: opentrain_send_message theme={null}
  {
    "conversationId": "<CONVERSATION_ID>",
    "content": "Hi Maria — your interview looked great. Could you start with a 5,000-post batch next week?"
  }
  ```
</RequestExample>

<ResponseExample>
  ```json 201 theme={null}
  {
    "ok": true,
    "message": {
      "id": "<MESSAGE_ID>",
      "createdAt": "2026-06-12T09:15:00.000Z",
      "updatedAt": "2026-06-12T09:15:00.000Z",
      "conversationId": "<CONVERSATION_ID>",
      "jobId": null,
      "senderUserId": "<YOUR_USER_ID>",
      "isFromViewer": true,
      "content": "Hi Maria — your interview looked great. Could you start with a 5,000-post batch next week?",
      "messageType": null,
      "system": false,
      "hasUnreadFlag": false,
      "attachmentCount": 0,
      "hasAudio": false,
      "editedAt": null,
      "reactionCount": 0
    }
  }
  ```

  ```json 429 (rate limited) theme={null}
  {
    "error": "Rate limit exceeded. Try again in 42s.",
    "code": "RATE_LIMITED",
    "requestId": "<REQUEST_ID>",
    "details": {
      "conversationId": "<CONVERSATION_ID>",
      "retryAfterSeconds": 42
    }
  }
  ```
</ResponseExample>
