GET /api/conversations/c-42/messages?before=opaque-cursor&limit=30
POST /api/conversations/c-42/messages
Content-Type: application/json
{
"clientMessageId": "local-8f93",
"text": "Ready for the review?"
}
GET /api/conversations/c-42/events?after=918
GET /api/conversations/c-42/messages/by-client-id/local-8f93
{
"eventId": "evt-919",
"conversationId": "c-42",
"eventSequence": 919,
"type": "message.created",
"message": {
"id": "m-301",
"clientMessageId": "local-8f93",
"senderId": "u-7",
"sequence": 301,
"text": "Ready for the review?",
"sentAt": "2026-10-03T09:00:00Z",
"version": 1
}
}
The server enforces uniqueness of the client message ID within the authenticated sender and conversation. Reusing it with the same payload returns the original message; reusing it with a different payload is a conflict. This is an application-level deduplication contract, not an automatic guarantee of POST. Define the retention window so a retry cannot silently become a second send.
Separate eventSequence, which covers durable edits, deletions, and receipts, from a message's position. History pages return opaque cursors and stable IDs. Validate event envelopes before applying them; an unknown event type should trigger a safe compatibility policy rather than corrupting the store.