DEVELOPER DOCUMENTATION
Send Message
Send a plain-text message into an existing conversation you participate in.
/api/public/v1/messagesSends 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
Section titled “Request”conversationIdstringbodyrequiredExisting conversation you participate in.
contentstringbodyrequiredPlain-text message, 1–10,000 characters after trimming. Send the raw text: real line breaks are kept exactly as sent. Do not escape line breaks. Literal \n or \r\n sequences outside Markdown inline or fenced code are rejected with 400 and code: "LIKELY_ESCAPED_NEWLINES". The error details give the number of occurrences, the position of the first one, a short sample, and a remediation hint. On the CLI, use --content-file <path>, or --content-file - to read from stdin, for multiline text.
allowLiteralEscapesbooleanbodyfalseSet to true to keep literal \n sequences as typed instead of failing. The response then carries a LITERAL_ESCAPES_ALLOWED warning. Real line breaks never need this. CLI: --allow-literal-escapes.
Response
Section titled “Response”Returns 201 on success.
okbooleantrue on success.
messageobjectThe 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, attachments, hasAudio, editedAt, reactionCount}.
deliveryobjectContent-integrity readback, so you can check that the stored text matches what you sent: schemaVersion, rawSha256 (SHA-256 of your raw submission), canonicalSha256 (of the trimmed value), storedSha256 (of the body read back from storage), exactRawMatch, canonicalMatch, warnings, and replayed. Expected trimming reads as exactRawMatch: false with canonicalMatch: true and a CANONICAL_TRIM_APPLIED warning. That is not corruption. Other warnings are CRLF_LINE_ENDINGS (stored as sent) and LITERAL_ESCAPES_ALLOWED.
Errors
Section titled “Errors”| Status | code | Meaning |
|---|---|---|
400 | BAD_REQUEST | Empty body, invalid JSON, or invalid fields (details.issues carries zod details), or content that is empty after trimming or longer than 10,000 characters (details.reason is EMPTY_CONTENT or CONTENT_TOO_LONG) |
400 | LIKELY_ESCAPED_NEWLINES | content contains literal \n sequences outside Markdown code. Send real line breaks, or set allowLiteralEscapes: true if the characters are intentional |
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 |