Skip to main content
POST
/
api
/
public
/
v1
/
messages
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?"
  }'
{
  "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
  }
}
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 or by starting a proposal thread. 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

conversationId
string
required
Existing conversation you participate in.
content
string
required
Plain-text message, 1–10,000 characters.

Response

Returns 201 on success.
ok
boolean
true on success.
message
object
The created message, in the same shape as the message entries on GET /messages: {id, createdAt, updatedAt, conversationId, jobId, senderUserId, isFromViewer, content, messageType, system, hasUnreadFlag, attachmentCount, hasAudio, editedAt, reactionCount}.

Errors

StatuscodeMeaning
400BAD_REQUESTEmpty body, invalid JSON, or invalid fields (details.issues carries zod details — e.g. content empty or over 10,000 chars)
401UNAUTHORIZEDMissing or invalid token
403FORBIDDENMissing 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)
404NOT_FOUNDNo such conversation
409CONFLICTSend blocked — details.reason is employer_first_message_required (candidate replying before the employer’s first message), read_only_conversation, or message_blocked
429RATE_LIMITEDMessaging rate limit hit — retry after details.retryAfterSeconds
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?"
  }'
{
  "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
  }
}