Messages
List, read, search, organize and snooze messages and threads.
- GETList messages
/v1/messages - GETWatch for new messages
/v1/messages/stream - GETRead a message
/v1/messages/{messageId} - POSTMark messages read
/v1/messages/read - POSTMark messages unread
/v1/messages/unread - POSTArchive messages
/v1/messages/archive - POSTUnarchive messages
/v1/messages/unarchive - POSTMove messages to Trash
/v1/messages/trash - POSTSearch messages
/v1/messages/search - POSTMove messages between sources
/v1/messages/move - PUTSnooze a message
/v1/messages/{messageId}/snooze - DELETECancel a snooze
/v1/messages/{messageId}/snooze - GETRead a thread
/v1/threads/{threadId}
List messages
GET/v1/messages
Lists messages from every source, or from one, most recently updated first. With in=snoozed, the message coming back soonest is first. A thread shows up once, as its latest message. For the next page, pass nextCursor as cursor; new messages can shift results between pages.
AuthAPI token
Query parameters
instring · default allallarchivesnoozedWhich messages to list.
all, the default, lists messages that are neither archived nor snoozed;archivelists archived messages; andsnoozedlists messages waiting to come back.sourceIdstring · starts with src_A source ID, for only that source's messages. Use Inbox's ID, from
GET/v1/, for Inbox. Leave it out for every source.sources unreadbooleantruelists only unread messages andfalseonly read ones. Leave it out for both. A thread is read or unread as a whole, as itsreadAtshows, and counts once, the same way source unread counts do.cursorstringThe
nextCursorfrom the previous page. Keep the other filters the same.limitinteger · 1–100 · default 50The most items to return in one page.
Responses
- 200
One page of messages
Response fields
itemslist of objectsRequiredFields
messageIdstringRequiredsourceobjectRequiredThe source a message belongs to. Inbox is the built-in
systemsource; sources you create arecustom.Fields
kindstringRequiredsystemcustomidstring · starts with src_RequiredlabelstringRequired
titlestring · can be nullRequiredA thread message's thread title; otherwise the sent title, or the body's first line when the message was sent without one.
previewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.alertstring · can be nullRequiredquietstandardimportantsoundstring · can be nullhoparcadeclinkdropechoglowknockliftsparkleswooptickwaveThe sound the sender chose for this message. Null when it plays its source's sound or the account's.
linkstring · URL · can be nullRequiredlinkTitlestring · up to 64 characters · can be nullfirstReceivedAtstring · date and timeRequiredWhen Ripple first received the message. It never changes.
receivedAtstring · date and timeRequiredThe message's date in lists. It starts as the time the message arrived and moves forward when the message comes back from a snooze.
updatedAtstring · date and timeRequiredrevisioninteger · at least 1RequiredGoes up each time the message changes. Send it as
expectedRevisionto act only on the version you read.snoozeobject · can be nullRequiredFields
snoozeIdstringRequiredreturnAtstring · date and timeRequiredtimeZonestringRequiredalertstringRequiredquietstandardimportantgenerationinteger · at least 1RequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
readAtstring · date and time · can be nullarchivedAtstring · date and time · can be nullthreadobjectFields
idstring · starts with thr_Requiredtitlestring · up to 120 charactersRequiredlatestMessageIdstringRequiredlatestMessageNumberinteger · at least 1RequiredmessageCountinteger · at least 1RequiredlastSeenMessageNumberinteger · at least 0RequiredunseenMessageCountinteger · at least 0RequiredfirstUnseenMessageNumberinteger · at least 1 · can be nullRequiredmanuallyUnreadbooleanRequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
matchobjectIn a search result for a thread, the message that matched, when it's an earlier message in the thread rather than the latest.
Fields
messageIdstringRequiredmessageNumberinteger · at least 1Required
previousMessageobjectFor a message in a thread, the message before it. Present only when the thread has an earlier message.
Fields
messageIdstringRequiredmessageNumberinteger · at least 1RequiredpreviewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.alertstring · can be nullRequiredquietstandardimportant
totalinteger · at least 0RequiredHow many messages match the filters, across all pages.
nextCursorstring · can be nullRequiredPass this as
cursorto get the next page.nullon the last page.
- 400
inisn'tall,archiveorsnoozed(invalid_place),unreadisn'ttrueorfalse(invalid_unread_filter), or the cursor came from a different list (invalid_cursor) - 401
The request has no valid API token or sign-in (
unauthorized) - 404
The source was not found (
not_found) - 503
Search is at capacity (
search_busy), the search took too long (search_timeout), or Ripple is busy (database_busy). Wait the number of seconds inRetry-After, then try again.
curl https://api.ripplepush.com/v1/messages \
-H "Authorization: Bearer $RIPPLE_KEY"const response = await fetch("https://api.ripplepush.com/v1/messages", {
headers: {
Authorization: `Bearer ${process.env.RIPPLE_KEY}`,
},
});
console.log(response.status, await response.text());import os
import urllib.request
request = urllib.request.Request(
"https://api.ripplepush.com/v1/messages",
headers={
"Authorization": "Bearer " + os.environ["RIPPLE_KEY"],
},
method="GET",
)
with urllib.request.urlopen(request, timeout=30) as response:
print(response.status, response.read().decode()){
"items": [
{
"messageId": "msg_2fe56d9f692c4febb604bb2c2c884ae7",
"source": {
"kind": "custom",
"id": "src_aa960551ca5b4ec68f82747db1f75a3b",
"label": "Deploy notifications"
},
"title": "Release progress",
"preview": "Build completed.",
"alert": "standard",
"link": null,
"firstReceivedAt": "2026-10-03T13:31:07.219Z",
"receivedAt": "2026-10-03T13:31:07.219Z",
"updatedAt": "2026-10-03T13:31:07.219Z",
"readAt": null,
"archivedAt": null,
"revision": 1,
"snooze": null,
"thread": {
"id": "thr_e91d5ec03ea9489d89a302645ccca63b",
"title": "Release progress",
"latestMessageId": "msg_2fe56d9f692c4febb604bb2c2c884ae7",
"latestMessageNumber": 1,
"messageCount": 1,
"lastSeenMessageNumber": 0,
"unseenMessageCount": 1,
"firstUnseenMessageNumber": 1,
"manuallyUnread": false,
"createdAt": "2026-10-03T13:31:07.219Z",
"updatedAt": "2026-10-03T13:31:07.219Z"
}
},
{
"messageId": "msg_0575320416fe4544b646044392618fbd",
"source": {
"kind": "system",
"id": "src_9fddbdcb785848c6b10b068facd32d87",
"label": "Inbox"
},
"title": "Deployment complete",
"preview": "Version 1.2 is ready.",
"alert": "standard",
"link": null,
"firstReceivedAt": "2026-10-03T13:31:07.201Z",
"receivedAt": "2026-10-03T13:31:07.201Z",
"updatedAt": "2026-10-03T13:31:07.201Z",
"readAt": null,
"archivedAt": null,
"revision": 1,
"snooze": null
}
],
"total": 2,
"nextCursor": null
}{
"code": "unauthorized",
"message": "Send a valid API token, or sign in."
}One page of messages
Watch for new messages
GET/v1/messages/stream
Keeps the connection open and sends each new message as it arrives, as Server-Sent Events. A new message is one Ripple accepts, including a new message in a thread; reading, archiving, snoozing, moving and Trash send nothing. Each message event's data is one line of JSON: the message as GET /v1/ describes a message, but always the one that arrived, without sourceState or sourceDeletedAt. A connection starts at the present. Every event has an id; reconnect with the last one in Last-Event-ID to receive every new message after it, for up to 30 days. An id with no data moves your place forward without a message. A comment line every 15 seconds keeps the connection open. The connection can close at any time, for example after 15 minutes, so reconnect when it does. Ignore events with other names.
AuthAPI token
Query parameters
sourceIdstring · starts with src_A source ID, for only that source's messages. Use Inbox's ID, from
GET/v1/, for Inbox. Leave it out for every source.sources
Headers
Last-Event-IDstringThe
idof the last event received, to resume after it. Leave it out to start at the present.
Responses
- 200
An open stream of new messages
- 401
The request has no valid API token or sign-in (
unauthorized) - 404
The source was not found (
not_found) - 410
Last-Event-IDis more than 30 days old or isn't one Ripple sent to this account (last_event_id_expired). Reconnect without it, and list messages to catch up. - 429
The account has too many streams open (
message_stream_limit). Retry after the indicated delay. - 503
Ripple is busy right now (
database_busy). Wait the number of seconds inRetry-After, then try again.
curl https://api.ripplepush.com/v1/messages/stream \
-H "Authorization: Bearer $RIPPLE_KEY"const response = await fetch("https://api.ripplepush.com/v1/messages/stream", {
headers: {
Authorization: `Bearer ${process.env.RIPPLE_KEY}`,
},
});
console.log(response.status, await response.text());import os
import urllib.request
request = urllib.request.Request(
"https://api.ripplepush.com/v1/messages/stream",
headers={
"Authorization": "Bearer " + os.environ["RIPPLE_KEY"],
},
method="GET",
)
with urllib.request.urlopen(request, timeout=30) as response:
print(response.status, response.read().decode())"id: AQ2zvT3fX0Rk8yq1c8h0b2NtJ4kT3PnRj6UeB-wJ2pYc1nq9Vd0aXg2nN4lD7c0F\nevent: message\ndata: {\"messageId\":\"msg_2fe56d9f692c4febb604bb2c2c884ae7\",\"source\":{\"kind\":\"custom\",\"id\":\"src_aa960551ca5b4ec68f82747db1f75a3b\",\"label\":\"Deploy notifications\"},\"title\":\"Deploy failed\",\"body\":\"Tests failed on main.\",\"link\":null,\"linkTitle\":null,\"alert\":\"important\",\"sound\":null,\"firstReceivedAt\":\"2026-10-03T13:31:07.219Z\",\"receivedAt\":\"2026-10-03T13:31:07.219Z\",\"updatedAt\":\"2026-10-03T13:31:07.219Z\",\"readAt\":null,\"archivedAt\":null,\"revision\":1,\"snooze\":null}\n\n: keep-alive\n"{
"code": "unauthorized",
"message": "Send a valid API token, or sign in."
}An open stream of new messages
Read a message
GET/v1/messages/{messageId}
Returns a message and its history in timeline. For a message in a thread, message shows the thread's latest state, requestedMessage names the message you asked for, and timeline holds the thread's newest messages.
To read further back, pass timelineCursor as cursor to GET /v1/. To jump to the page that ends at the message you asked for, pass requestedMessage.cursor instead.
AuthAPI token
Path parameters
messageIdstringRequired
Responses
- 200
The message and its timeline
Response fields
messageobjectRequiredFields
messageIdstringRequiredsourceobjectRequiredThe source a message belongs to. Inbox is the built-in
systemsource; sources you create arecustom.Fields
kindstringRequiredsystemcustomidstring · starts with src_RequiredlabelstringRequired
titlestring · can be nullRequiredpreviewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.bodystring · can be nullRequiredlinkstring · URL · can be nullRequiredlinkTitlestring · up to 64 characters · can be nullalertstring · can be nullRequiredquietstandardimportantsoundstring · can be nullhoparcadeclinkdropechoglowknockliftsparkleswooptickwaveThe sound the sender chose for this message. Null when it plays its source's sound or the account's.
firstReceivedAtstring · date and timeRequiredWhen Ripple first received the message. It never changes.
receivedAtstring · date and timeRequiredThe message's date in lists. It starts as the time the message arrived and moves forward when the message comes back from a snooze.
updatedAtstring · date and timeRequiredrevisioninteger · at least 1RequiredGoes up each time the message changes. Send it as
expectedRevisionto act only on the version you read.snoozeobject · can be nullRequiredFields
snoozeIdstringRequiredreturnAtstring · date and timeRequiredtimeZonestringRequiredalertstringRequiredquietstandardimportantgenerationinteger · at least 1RequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
readAtstring · date and time · can be nullarchivedAtstring · date and time · can be nullthreadobjectFields
idstring · starts with thr_Requiredtitlestring · up to 120 charactersRequiredlatestMessageIdstringRequiredlatestMessageNumberinteger · at least 1RequiredmessageCountinteger · at least 1RequiredlastSeenMessageNumberinteger · at least 0RequiredunseenMessageCountinteger · at least 0RequiredfirstUnseenMessageNumberinteger · at least 1 · can be nullRequiredmanuallyUnreadbooleanRequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
previousMessageobjectFor a message in a thread, the message before it, as in message lists. Present only when the thread has an earlier message.
Fields
messageIdstringRequiredmessageNumberinteger · at least 1RequiredpreviewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.alertstring · can be nullRequiredquietstandardimportant
sourceStatestringRequiredactivetrashdeletedsourceDeletedAtstring · date and time · can be nullRequired
requestedMessageobjectThe message you asked for, when it's in a thread.
messagestill shows the thread's latest state.Fields
messageIdstringRequiredmessageNumberinteger · at least 1RequiredcursorstringRequiredPass this as
cursortoGET/v1/for the page that ends at this message: the message, the ones before it, and athreads/ {threadId} nextCursorto keep going.
timelinelist of objectsRequiredFields
eventIdstringRequiredeventTypestringRequiredacceptedreadunreadarchivedunarchiveddeletedrestoredsource_changedsnoozedsnooze_changedsnooze_canceledalert_changedcreatedAtstring · date and timeRequiredsourceobjectRequiredThe source a message belongs to. Inbox is the built-in
systemsource; sources you create arecustom.Fields
kindstringRequiredsystemcustomidstring · starts with src_RequiredlabelstringRequired
messageIdstringmessageNumberinteger · at least 1titlestring · can be nullbodystring · can be nulllinkstring · URL · can be nulllinkTitlestring · up to 64 characters · can be nullalertstring · can be nullquietstandardimportantmetadataobject · can be nullRequiredFields
threadTitlestring · can be nullThe thread's title when the message arrived, or
nullfor a message that isn't in a thread.snoozeIdstringgenerationinteger · at least 1returnAtstring · date and timetimeZonestringpreviousReturnAtstring · date and timepreviousTimeZonestringalertstringquietstandardimportantfromstring · can be nullquietstandardimportanttostringquietstandardimportantfromSourceobjectThe source a message belongs to. Inbox is the built-in
systemsource; sources you create arecustom.Fields
kindstringRequiredsystemcustomidstring · starts with src_RequiredlabelstringRequired
toSourceobjectThe source a message belongs to. Inbox is the built-in
systemsource; sources you create arecustom.Fields
kindstringRequiredsystemcustomidstring · starts with src_RequiredlabelstringRequired
timelineCursorstring · can be nullRequiredPass this as
cursortoGET/v1/to read further back thanthreads/ {threadId} timelinegoes.nullwhentimelinealready holds the whole thread, or the message isn't in a thread.
- 401
The request has no valid API token or sign-in (
unauthorized) - 404
The message was not found (
not_found) - 503
Ripple is busy right now (
database_busy). Wait the number of seconds inRetry-After, then try again.
curl https://api.ripplepush.com/v1/messages/MESSAGE_ID \
-H "Authorization: Bearer $RIPPLE_KEY"const response = await fetch("https://api.ripplepush.com/v1/messages/MESSAGE_ID", {
headers: {
Authorization: `Bearer ${process.env.RIPPLE_KEY}`,
},
});
console.log(response.status, await response.text());import os
import urllib.request
request = urllib.request.Request(
"https://api.ripplepush.com/v1/messages/MESSAGE_ID",
headers={
"Authorization": "Bearer " + os.environ["RIPPLE_KEY"],
},
method="GET",
)
with urllib.request.urlopen(request, timeout=30) as response:
print(response.status, response.read().decode()){
"message": {
"messageId": "msg_0575320416fe4544b646044392618fbd",
"source": {
"kind": "custom",
"id": "src_aa960551ca5b4ec68f82747db1f75a3b",
"label": "Deploy notifications"
},
"title": "Deployment complete",
"preview": "Version 1.2 is ready.",
"body": "Version 1.2 is ready.",
"link": null,
"linkTitle": null,
"alert": "standard",
"firstReceivedAt": "2026-10-03T13:31:07.201Z",
"receivedAt": "2026-10-03T13:31:07.201Z",
"updatedAt": "2026-10-03T13:31:07.201Z",
"readAt": null,
"archivedAt": null,
"revision": 3,
"snooze": null,
"sourceDeletedAt": null,
"sourceState": "active"
},
"timeline": [
{
"eventId": "evt_b962b25063584cfaa75c3510f94d23b3",
"eventType": "unarchived",
"createdAt": "2026-10-03T13:31:07.328Z",
"source": {
"kind": "custom",
"id": "src_aa960551ca5b4ec68f82747db1f75a3b",
"label": "Deploy notifications"
},
"metadata": null,
"messageId": "msg_0575320416fe4544b646044392618fbd"
},
{
"eventId": "evt_2d808a8cfc3543d7a350f8e7fbade900",
"eventType": "archived",
"createdAt": "2026-10-03T13:31:07.315Z",
"source": {
"kind": "custom",
"id": "src_aa960551ca5b4ec68f82747db1f75a3b",
"label": "Deploy notifications"
},
"metadata": null,
"messageId": "msg_0575320416fe4544b646044392618fbd"
},
{
"eventId": "evt_51c8563239c544609b7e08f6dc319ac8",
"eventType": "accepted",
"createdAt": "2026-10-03T13:31:07.201Z",
"source": {
"kind": "custom",
"id": "src_aa960551ca5b4ec68f82747db1f75a3b",
"label": "Deploy notifications"
},
"metadata": {
"alert": "standard",
"threadTitle": null
},
"messageId": "msg_0575320416fe4544b646044392618fbd"
}
],
"timelineCursor": null
}{
"code": "unauthorized",
"message": "Send a valid API token, or sign in."
}The message and its timeline
Mark messages read
POST/v1/messages/read
Marks up to 100 messages read, all or none. A message in a thread marks its whole thread.
AuthAPI token
Body
messageIdslist of strings · 1–100 itemsRequired
Responses
- 200
The selected messages as they are now
Response fields
messageslist of objectsRequiredFields
messageIdstringRequiredsourceobjectRequiredThe source a message belongs to. Inbox is the built-in
systemsource; sources you create arecustom.Fields
kindstringRequiredsystemcustomidstring · starts with src_RequiredlabelstringRequired
titlestring · can be nullRequiredpreviewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.bodystring · can be nullRequiredlinkstring · URL · can be nullRequiredlinkTitlestring · up to 64 characters · can be nullalertstring · can be nullRequiredquietstandardimportantsoundstring · can be nullhoparcadeclinkdropechoglowknockliftsparkleswooptickwaveThe sound the sender chose for this message. Null when it plays its source's sound or the account's.
firstReceivedAtstring · date and timeRequiredWhen Ripple first received the message. It never changes.
receivedAtstring · date and timeRequiredThe message's date in lists. It starts as the time the message arrived and moves forward when the message comes back from a snooze.
updatedAtstring · date and timeRequiredrevisioninteger · at least 1RequiredGoes up each time the message changes. Send it as
expectedRevisionto act only on the version you read.snoozeobject · can be nullRequiredFields
snoozeIdstringRequiredreturnAtstring · date and timeRequiredtimeZonestringRequiredalertstringRequiredquietstandardimportantgenerationinteger · at least 1RequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
readAtstring · date and time · can be nullarchivedAtstring · date and time · can be nullthreadobjectFields
idstring · starts with thr_Requiredtitlestring · up to 120 charactersRequiredlatestMessageIdstringRequiredlatestMessageNumberinteger · at least 1RequiredmessageCountinteger · at least 1RequiredlastSeenMessageNumberinteger · at least 0RequiredunseenMessageCountinteger · at least 0RequiredfirstUnseenMessageNumberinteger · at least 1 · can be nullRequiredmanuallyUnreadbooleanRequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
previousMessageobjectFor a message in a thread, the message before it, as in message lists. Present only when the thread has an earlier message.
Fields
messageIdstringRequiredmessageNumberinteger · at least 1RequiredpreviewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.alertstring · can be nullRequiredquietstandardimportant
sourceStatestringactivetrashdeletedsourceDeletedAtstring · date and time · can be null
changedCountinteger · at least 0RequiredmutationAtstring · date and timeRequired
- 400
messageIdsis missing or empty, or has more than 100 IDs (invalid_message_ids) - 401
The request has no valid API token or sign-in (
unauthorized) - 403
The API token is read-only (
read_only_token), so it can only list, read and search. - 404
One of the messages was not found (
not_found) - 409
A message the request names, or its source, is in Trash (
message_deleted) - 503
Ripple is busy right now (
database_busy). Wait the number of seconds inRetry-After, then try again.
curl https://api.ripplepush.com/v1/messages/read \
-H "Authorization: Bearer $RIPPLE_KEY" \
-H "Content-Type: application/json" \
-d '{
"messageIds": [
"msg_0575320416fe4544b646044392618fbd"
]
}'const response = await fetch("https://api.ripplepush.com/v1/messages/read", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RIPPLE_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
messageIds: [
"msg_0575320416fe4544b646044392618fbd",
],
}),
});
console.log(response.status, await response.text());import json
import os
import urllib.request
request = urllib.request.Request(
"https://api.ripplepush.com/v1/messages/read",
data=json.dumps({
"messageIds": [
"msg_0575320416fe4544b646044392618fbd",
],
}).encode(),
headers={
"Authorization": "Bearer " + os.environ["RIPPLE_KEY"],
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(request, timeout=30) as response:
print(response.status, response.read().decode()){
"messages": [
{
"messageId": "msg_0575320416fe4544b646044392618fbd",
"source": {
"kind": "custom",
"id": "src_aa960551ca5b4ec68f82747db1f75a3b",
"label": "Deploy notifications"
},
"title": "Deployment complete",
"preview": "Version 1.2 is ready.",
"body": "Version 1.2 is ready.",
"link": null,
"linkTitle": null,
"alert": "standard",
"firstReceivedAt": "2026-10-03T13:31:07.201Z",
"receivedAt": "2026-10-03T13:31:07.201Z",
"updatedAt": "2026-10-03T13:31:07.201Z",
"readAt": "2026-10-03T13:31:07.280Z",
"archivedAt": null,
"revision": 1,
"snooze": null,
"sourceDeletedAt": null,
"sourceState": "active"
}
],
"changedCount": 1,
"mutationAt": "2026-10-03T13:31:07.280Z"
}{
"code": "unauthorized",
"message": "Send a valid API token, or sign in."
}{
"code": "read_only_token",
"message": "This API token can only read."
}The selected messages as they are now
Mark messages unread
POST/v1/messages/unread
Marks up to 100 messages unread, all or none. A message in a thread marks its whole thread.
AuthAPI token
Body
messageIdslist of strings · 1–100 itemsRequired
Responses
- 200
The selected messages as they are now
Response fields
messageslist of objectsRequiredFields
messageIdstringRequiredsourceobjectRequiredThe source a message belongs to. Inbox is the built-in
systemsource; sources you create arecustom.Fields
kindstringRequiredsystemcustomidstring · starts with src_RequiredlabelstringRequired
titlestring · can be nullRequiredpreviewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.bodystring · can be nullRequiredlinkstring · URL · can be nullRequiredlinkTitlestring · up to 64 characters · can be nullalertstring · can be nullRequiredquietstandardimportantsoundstring · can be nullhoparcadeclinkdropechoglowknockliftsparkleswooptickwaveThe sound the sender chose for this message. Null when it plays its source's sound or the account's.
firstReceivedAtstring · date and timeRequiredWhen Ripple first received the message. It never changes.
receivedAtstring · date and timeRequiredThe message's date in lists. It starts as the time the message arrived and moves forward when the message comes back from a snooze.
updatedAtstring · date and timeRequiredrevisioninteger · at least 1RequiredGoes up each time the message changes. Send it as
expectedRevisionto act only on the version you read.snoozeobject · can be nullRequiredFields
snoozeIdstringRequiredreturnAtstring · date and timeRequiredtimeZonestringRequiredalertstringRequiredquietstandardimportantgenerationinteger · at least 1RequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
readAtstring · date and time · can be nullarchivedAtstring · date and time · can be nullthreadobjectFields
idstring · starts with thr_Requiredtitlestring · up to 120 charactersRequiredlatestMessageIdstringRequiredlatestMessageNumberinteger · at least 1RequiredmessageCountinteger · at least 1RequiredlastSeenMessageNumberinteger · at least 0RequiredunseenMessageCountinteger · at least 0RequiredfirstUnseenMessageNumberinteger · at least 1 · can be nullRequiredmanuallyUnreadbooleanRequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
previousMessageobjectFor a message in a thread, the message before it, as in message lists. Present only when the thread has an earlier message.
Fields
messageIdstringRequiredmessageNumberinteger · at least 1RequiredpreviewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.alertstring · can be nullRequiredquietstandardimportant
sourceStatestringactivetrashdeletedsourceDeletedAtstring · date and time · can be null
changedCountinteger · at least 0RequiredmutationAtstring · date and timeRequired
- 400
messageIdsis missing or empty, or has more than 100 IDs (invalid_message_ids) - 401
The request has no valid API token or sign-in (
unauthorized) - 403
The API token is read-only (
read_only_token), so it can only list, read and search. - 404
One of the messages was not found (
not_found) - 409
A message the request names, or its source, is in Trash (
message_deleted) - 503
Ripple is busy right now (
database_busy). Wait the number of seconds inRetry-After, then try again.
curl https://api.ripplepush.com/v1/messages/unread \
-H "Authorization: Bearer $RIPPLE_KEY" \
-H "Content-Type: application/json" \
-d '{
"messageIds": [
"msg_0575320416fe4544b646044392618fbd"
]
}'const response = await fetch("https://api.ripplepush.com/v1/messages/unread", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RIPPLE_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
messageIds: [
"msg_0575320416fe4544b646044392618fbd",
],
}),
});
console.log(response.status, await response.text());import json
import os
import urllib.request
request = urllib.request.Request(
"https://api.ripplepush.com/v1/messages/unread",
data=json.dumps({
"messageIds": [
"msg_0575320416fe4544b646044392618fbd",
],
}).encode(),
headers={
"Authorization": "Bearer " + os.environ["RIPPLE_KEY"],
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(request, timeout=30) as response:
print(response.status, response.read().decode()){
"messages": [
{
"messageId": "msg_0575320416fe4544b646044392618fbd",
"source": {
"kind": "custom",
"id": "src_aa960551ca5b4ec68f82747db1f75a3b",
"label": "Deploy notifications"
},
"title": "Deployment complete",
"preview": "Version 1.2 is ready.",
"body": "Version 1.2 is ready.",
"link": null,
"linkTitle": null,
"alert": "standard",
"firstReceivedAt": "2026-10-03T13:31:07.201Z",
"receivedAt": "2026-10-03T13:31:07.201Z",
"updatedAt": "2026-10-03T13:31:07.201Z",
"readAt": null,
"archivedAt": null,
"revision": 1,
"snooze": null,
"sourceDeletedAt": null,
"sourceState": "active"
}
],
"changedCount": 1,
"mutationAt": "2026-10-03T13:31:07.300Z"
}{
"code": "unauthorized",
"message": "Send a valid API token, or sign in."
}{
"code": "read_only_token",
"message": "This API token can only read."
}The selected messages as they are now
Archive messages
POST/v1/messages/archive
Archives up to 100 messages, all or none. Archiving a snoozed message ends its snooze.
AuthAPI token
Body
messageIdslist of strings · 1–100 itemsRequired
Responses
- 200
The selected messages as they are now
Response fields
messageslist of objectsRequiredFields
messageIdstringRequiredsourceobjectRequiredThe source a message belongs to. Inbox is the built-in
systemsource; sources you create arecustom.Fields
kindstringRequiredsystemcustomidstring · starts with src_RequiredlabelstringRequired
titlestring · can be nullRequiredpreviewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.bodystring · can be nullRequiredlinkstring · URL · can be nullRequiredlinkTitlestring · up to 64 characters · can be nullalertstring · can be nullRequiredquietstandardimportantsoundstring · can be nullhoparcadeclinkdropechoglowknockliftsparkleswooptickwaveThe sound the sender chose for this message. Null when it plays its source's sound or the account's.
firstReceivedAtstring · date and timeRequiredWhen Ripple first received the message. It never changes.
receivedAtstring · date and timeRequiredThe message's date in lists. It starts as the time the message arrived and moves forward when the message comes back from a snooze.
updatedAtstring · date and timeRequiredrevisioninteger · at least 1RequiredGoes up each time the message changes. Send it as
expectedRevisionto act only on the version you read.snoozeobject · can be nullRequiredFields
snoozeIdstringRequiredreturnAtstring · date and timeRequiredtimeZonestringRequiredalertstringRequiredquietstandardimportantgenerationinteger · at least 1RequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
readAtstring · date and time · can be nullarchivedAtstring · date and time · can be nullthreadobjectFields
idstring · starts with thr_Requiredtitlestring · up to 120 charactersRequiredlatestMessageIdstringRequiredlatestMessageNumberinteger · at least 1RequiredmessageCountinteger · at least 1RequiredlastSeenMessageNumberinteger · at least 0RequiredunseenMessageCountinteger · at least 0RequiredfirstUnseenMessageNumberinteger · at least 1 · can be nullRequiredmanuallyUnreadbooleanRequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
previousMessageobjectFor a message in a thread, the message before it, as in message lists. Present only when the thread has an earlier message.
Fields
messageIdstringRequiredmessageNumberinteger · at least 1RequiredpreviewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.alertstring · can be nullRequiredquietstandardimportant
sourceStatestringactivetrashdeletedsourceDeletedAtstring · date and time · can be null
changedCountinteger · at least 0RequiredmutationAtstring · date and timeRequired
- 400
messageIdsis missing or empty, or has more than 100 IDs (invalid_message_ids) - 401
The request has no valid API token or sign-in (
unauthorized) - 403
The API token is read-only (
read_only_token), so it can only list, read and search. - 404
One of the messages was not found (
not_found) - 409
A message the request names, or its source, is in Trash (
message_deleted) - 503
Ripple is busy right now (
database_busy). Wait the number of seconds inRetry-After, then try again.
curl https://api.ripplepush.com/v1/messages/archive \
-H "Authorization: Bearer $RIPPLE_KEY" \
-H "Content-Type: application/json" \
-d '{
"messageIds": [
"msg_0575320416fe4544b646044392618fbd"
]
}'const response = await fetch("https://api.ripplepush.com/v1/messages/archive", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RIPPLE_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
messageIds: [
"msg_0575320416fe4544b646044392618fbd",
],
}),
});
console.log(response.status, await response.text());import json
import os
import urllib.request
request = urllib.request.Request(
"https://api.ripplepush.com/v1/messages/archive",
data=json.dumps({
"messageIds": [
"msg_0575320416fe4544b646044392618fbd",
],
}).encode(),
headers={
"Authorization": "Bearer " + os.environ["RIPPLE_KEY"],
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(request, timeout=30) as response:
print(response.status, response.read().decode()){
"messages": [
{
"messageId": "msg_0575320416fe4544b646044392618fbd",
"source": {
"kind": "custom",
"id": "src_aa960551ca5b4ec68f82747db1f75a3b",
"label": "Deploy notifications"
},
"title": "Deployment complete",
"preview": "Version 1.2 is ready.",
"body": "Version 1.2 is ready.",
"link": null,
"linkTitle": null,
"alert": "standard",
"firstReceivedAt": "2026-10-03T13:31:07.201Z",
"receivedAt": "2026-10-03T13:31:07.201Z",
"updatedAt": "2026-10-03T13:31:07.201Z",
"readAt": null,
"archivedAt": "2026-10-03T13:31:07.315Z",
"revision": 2,
"snooze": null,
"sourceDeletedAt": null,
"sourceState": "active"
}
],
"changedCount": 1,
"mutationAt": "2026-10-03T13:31:07.315Z"
}{
"code": "unauthorized",
"message": "Send a valid API token, or sign in."
}{
"code": "read_only_token",
"message": "This API token can only read."
}The selected messages as they are now
Unarchive messages
POST/v1/messages/unarchive
Unarchives up to 100 messages, all or none. Each message keeps its source, so it goes back to All Messages (in=all) and its own source, and to Inbox only if Inbox is its source.
AuthAPI token
Body
messageIdslist of strings · 1–100 itemsRequired
Responses
- 200
The selected messages as they are now
Response fields
messageslist of objectsRequiredFields
messageIdstringRequiredsourceobjectRequiredThe source a message belongs to. Inbox is the built-in
systemsource; sources you create arecustom.Fields
kindstringRequiredsystemcustomidstring · starts with src_RequiredlabelstringRequired
titlestring · can be nullRequiredpreviewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.bodystring · can be nullRequiredlinkstring · URL · can be nullRequiredlinkTitlestring · up to 64 characters · can be nullalertstring · can be nullRequiredquietstandardimportantsoundstring · can be nullhoparcadeclinkdropechoglowknockliftsparkleswooptickwaveThe sound the sender chose for this message. Null when it plays its source's sound or the account's.
firstReceivedAtstring · date and timeRequiredWhen Ripple first received the message. It never changes.
receivedAtstring · date and timeRequiredThe message's date in lists. It starts as the time the message arrived and moves forward when the message comes back from a snooze.
updatedAtstring · date and timeRequiredrevisioninteger · at least 1RequiredGoes up each time the message changes. Send it as
expectedRevisionto act only on the version you read.snoozeobject · can be nullRequiredFields
snoozeIdstringRequiredreturnAtstring · date and timeRequiredtimeZonestringRequiredalertstringRequiredquietstandardimportantgenerationinteger · at least 1RequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
readAtstring · date and time · can be nullarchivedAtstring · date and time · can be nullthreadobjectFields
idstring · starts with thr_Requiredtitlestring · up to 120 charactersRequiredlatestMessageIdstringRequiredlatestMessageNumberinteger · at least 1RequiredmessageCountinteger · at least 1RequiredlastSeenMessageNumberinteger · at least 0RequiredunseenMessageCountinteger · at least 0RequiredfirstUnseenMessageNumberinteger · at least 1 · can be nullRequiredmanuallyUnreadbooleanRequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
previousMessageobjectFor a message in a thread, the message before it, as in message lists. Present only when the thread has an earlier message.
Fields
messageIdstringRequiredmessageNumberinteger · at least 1RequiredpreviewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.alertstring · can be nullRequiredquietstandardimportant
sourceStatestringactivetrashdeletedsourceDeletedAtstring · date and time · can be null
changedCountinteger · at least 0RequiredmutationAtstring · date and timeRequired
- 400
messageIdsis missing or empty, or has more than 100 IDs (invalid_message_ids) - 401
The request has no valid API token or sign-in (
unauthorized) - 403
The API token is read-only (
read_only_token), so it can only list, read and search. - 404
One of the messages was not found (
not_found) - 409
A message the request names, or its source, is in Trash (
message_deleted) - 503
Ripple is busy right now (
database_busy). Wait the number of seconds inRetry-After, then try again.
curl https://api.ripplepush.com/v1/messages/unarchive \
-H "Authorization: Bearer $RIPPLE_KEY" \
-H "Content-Type: application/json" \
-d '{
"messageIds": [
"msg_0575320416fe4544b646044392618fbd"
]
}'const response = await fetch("https://api.ripplepush.com/v1/messages/unarchive", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RIPPLE_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
messageIds: [
"msg_0575320416fe4544b646044392618fbd",
],
}),
});
console.log(response.status, await response.text());import json
import os
import urllib.request
request = urllib.request.Request(
"https://api.ripplepush.com/v1/messages/unarchive",
data=json.dumps({
"messageIds": [
"msg_0575320416fe4544b646044392618fbd",
],
}).encode(),
headers={
"Authorization": "Bearer " + os.environ["RIPPLE_KEY"],
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(request, timeout=30) as response:
print(response.status, response.read().decode()){
"messages": [
{
"messageId": "msg_0575320416fe4544b646044392618fbd",
"source": {
"kind": "custom",
"id": "src_aa960551ca5b4ec68f82747db1f75a3b",
"label": "Deploy notifications"
},
"title": "Deployment complete",
"preview": "Version 1.2 is ready.",
"body": "Version 1.2 is ready.",
"link": null,
"linkTitle": null,
"alert": "standard",
"firstReceivedAt": "2026-10-03T13:31:07.201Z",
"receivedAt": "2026-10-03T13:31:07.201Z",
"updatedAt": "2026-10-03T13:31:07.201Z",
"readAt": null,
"archivedAt": null,
"revision": 3,
"snooze": null,
"sourceDeletedAt": null,
"sourceState": "active"
}
],
"changedCount": 1,
"mutationAt": "2026-10-03T13:31:07.328Z"
}{
"code": "unauthorized",
"message": "Send a valid API token, or sign in."
}{
"code": "read_only_token",
"message": "This API token can only read."
}The selected messages as they are now
Move messages to Trash
POST/v1/messages/trash
Moves up to 100 messages to Trash, all or none. You can restore them for 30 days; after that they're deleted forever. A message in a thread moves its whole thread. A message already in Trash is skipped and not counted.
AuthAPI token
Body
messageIdslist of strings · 1–100 itemsRequired
Responses
- 200
The messages this request moved to Trash
Response fields
resultslist of objectsRequiredEach message this request moved to Trash, with its thread's ID when it belongs to one.
Fields
messageIdstringRequiredthreadIdstring · can be nullRequired
changedCountinteger · at least 0RequiredHow many messages and threads this request moved to Trash. Ones already there aren't counted.
mutationAtstring · date and timeRequired
- 400
messageIdsis missing or empty, or has more than 100 IDs (invalid_message_ids) - 401
The request has no valid API token or sign-in (
unauthorized) - 403
The API token is read-only (
read_only_token), so it can only list, read and search. - 404
One of the messages was not found (
not_found) - 409
A selected message's source is in Trash (
message_deleted) - 503
Ripple is busy right now (
database_busy). Wait the number of seconds inRetry-After, then try again.
curl https://api.ripplepush.com/v1/messages/trash \
-H "Authorization: Bearer $RIPPLE_KEY" \
-H "Content-Type: application/json" \
-d '{
"messageIds": [
"msg_0575320416fe4544b646044392618fbd"
]
}'const response = await fetch("https://api.ripplepush.com/v1/messages/trash", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RIPPLE_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
messageIds: [
"msg_0575320416fe4544b646044392618fbd",
],
}),
});
console.log(response.status, await response.text());import json
import os
import urllib.request
request = urllib.request.Request(
"https://api.ripplepush.com/v1/messages/trash",
data=json.dumps({
"messageIds": [
"msg_0575320416fe4544b646044392618fbd",
],
}).encode(),
headers={
"Authorization": "Bearer " + os.environ["RIPPLE_KEY"],
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(request, timeout=30) as response:
print(response.status, response.read().decode()){
"results": [
{
"messageId": "msg_2fe56d9f692c4febb604bb2c2c884ae7",
"threadId": "thr_e91d5ec03ea9489d89a302645ccca63b"
}
],
"changedCount": 1,
"mutationAt": "2026-10-03T13:31:07.788Z"
}{
"code": "unauthorized",
"message": "Send a valid API token, or sign in."
}{
"code": "read_only_token",
"message": "This API token can only read."
}The messages this request moved to Trash
Search messages
POST/v1/messages/search
Searches the titles, bodies, thread titles and source names of messages in every source. Active, archived and snoozed messages are included; Trash is not. A thread shows up once, at its best-matching message. Results rank by how well they match (an exact title first, then the whole query together in one field, then every word in one field), then newest first by receivedAt.
Filters narrow results by place, read state, source, alert and date, and work without a query. When a word matches nothing, Ripple also tries one-letter corrections taken only from your own messages and lists them in corrections. For the next page, pass nextCursor as cursor; new messages can shift results between pages.
AuthAPI token
Body
instringallarchivesnoozedSearches one place only:
allfor All Messages (neither archived nor snoozed),archivefor archived messages, orsnoozedfor messages waiting to come back. A thread is where the thread is, whichever of its messages matched. Leave it out to search everywhere except Trash.querystring · up to 500 charactersThe words to search for, up to 64. Every word must match, and the last can be the start of a word, so
deplfindsdeploy. Put the whole query in double quotes to find the words together in one field. Words with digits or symbols, such as IDs, paths and percentages, match exactly. Typo correction only runs on unquoted searches of 12 words or fewer. Leave it out or blank to get everything the filters allow, newest first.unreadbooleantruefinds only unread messages andfalseonly read ones. Leave it out for both. A thread is read or unread as a whole, as its result'sreadAtshows, whichever of its messages matched.sourceIdslist of strings · 1–100 itemsSearches only these sources, by ID. Use Inbox's ID for Inbox.
afterstring · date and timeFinds only messages whose
receivedAtis at or after this time.beforestring · date and timeFinds only messages whose
receivedAtis before this time. If you also sendafter, this must be later.sortstringrelevancenewestrelevance, the default, ranks by how well each result matches, then newest first byreceivedAt.newestsorts byreceivedAtalone and places a thread at its newest matching message. A search without a query is always newest first.alertstringquietstandardimportantFinds only messages whose alert is now this one. Messages sent without an alert count as
standard.limitinteger · 1–100 · default 50The most results to return in one page.
cursorstring · 1–200 charactersThe
nextCursorfrom the previous page. Keep the query and filters the same.
Responses
- 200
One page of ranked results
Response fields
itemslist of objectsRequiredFields
messageobjectRequiredThe matching message as it appears in lists. For a thread, this is the thread's latest message, and
matchnames the earlier message that matched, if it wasn't the latest.Fields
messageIdstringRequiredsourceobjectRequiredThe source a message belongs to. Inbox is the built-in
systemsource; sources you create arecustom.Fields
kindstringRequiredsystemcustomidstring · starts with src_RequiredlabelstringRequired
titlestring · can be nullRequiredA thread message's thread title; otherwise the sent title, or the body's first line when the message was sent without one.
previewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.alertstring · can be nullRequiredquietstandardimportantsoundstring · can be nullhoparcadeclinkdropechoglowknockliftsparkleswooptickwaveThe sound the sender chose for this message. Null when it plays its source's sound or the account's.
linkstring · URL · can be nullRequiredlinkTitlestring · up to 64 characters · can be nullfirstReceivedAtstring · date and timeRequiredWhen Ripple first received the message. It never changes.
receivedAtstring · date and timeRequiredThe message's date in lists. It starts as the time the message arrived and moves forward when the message comes back from a snooze.
updatedAtstring · date and timeRequiredrevisioninteger · at least 1RequiredGoes up each time the message changes. Send it as
expectedRevisionto act only on the version you read.snoozeobject · can be nullRequiredFields
snoozeIdstringRequiredreturnAtstring · date and timeRequiredtimeZonestringRequiredalertstringRequiredquietstandardimportantgenerationinteger · at least 1RequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
readAtstring · date and time · can be nullarchivedAtstring · date and time · can be nullthreadobjectFields
idstring · starts with thr_Requiredtitlestring · up to 120 charactersRequiredlatestMessageIdstringRequiredlatestMessageNumberinteger · at least 1RequiredmessageCountinteger · at least 1RequiredlastSeenMessageNumberinteger · at least 0RequiredunseenMessageCountinteger · at least 0RequiredfirstUnseenMessageNumberinteger · at least 1 · can be nullRequiredmanuallyUnreadbooleanRequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
matchobjectIn a search result for a thread, the message that matched, when it's an earlier message in the thread rather than the latest.
Fields
messageIdstringRequiredmessageNumberinteger · at least 1Required
previousMessageobjectFor a message in a thread, the message before it. Present only when the thread has an earlier message.
Fields
messageIdstringRequiredmessageNumberinteger · at least 1RequiredpreviewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.alertstring · can be nullRequiredquietstandardimportant
excerptobjectRequiredPlain text from the matching message's body, around the first match. It never contains markup.
Fields
textstringRequiredtruncatedStartbooleanRequiredtruncatedEndbooleanRequired
nextCursorstring · can be nullRequiredPass this as
cursorfor the next page. It'snullwhen there are no more matches, or when the search has reached its limit: one search returns at most 1,000 results plus one page. Checktruncatedbefore you treat the results as complete.truncatedbooleanRequiredtruewhen more matches exist but the search can't return another page, sonextCursorisnull. Narrow the query or filters to reach them.falsedoesn't mean you have everything whilenextCursoris still set. This is separate fromcoverage.correctionslist of objectsRequiredFields
originalstringRequiredalternativeslist of strings · 1–4 itemsRequired
coveragestringRequiredcompleteindexingindexingwhile Ripple is building or rebuilding the search index, so results cover only the messages indexed so far.completemeans every message is indexed. It says nothing about the result limit; checktruncatedfor that.
- 400
The request is malformed or its filters conflict (
invalid_search_request), the cursor came from a different search (invalid_cursor), or the query has more than 64 words (too_many_terms) - 401
The request has no valid API token or sign-in (
unauthorized) - 503
Search is at capacity (
search_busy), the search took too long (search_timeout), or Ripple is busy (database_busy). Wait the number of seconds inRetry-After, then try again.
curl https://api.ripplepush.com/v1/messages/search \
-H "Authorization: Bearer $RIPPLE_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "Deployment"
}'const response = await fetch("https://api.ripplepush.com/v1/messages/search", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RIPPLE_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: "Deployment",
}),
});
console.log(response.status, await response.text());import json
import os
import urllib.request
request = urllib.request.Request(
"https://api.ripplepush.com/v1/messages/search",
data=json.dumps({
"query": "Deployment",
}).encode(),
headers={
"Authorization": "Bearer " + os.environ["RIPPLE_KEY"],
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(request, timeout=30) as response:
print(response.status, response.read().decode()){
"items": [],
"nextCursor": null,
"truncated": false,
"corrections": [],
"coverage": "complete"
}{
"code": "unauthorized",
"message": "Send a valid API token, or sign in."
}One page of ranked results
Move messages between sources
POST/v1/messages/move
Moves up to 100 messages to other sources, all or none. Each message keeps its ID, history, read and archive state and snooze. A message already in its target source comes back with changed: false and adds nothing to its history. Messages in threads stay with their thread's source and can't be moved.
AuthAPI token
Body
moveslist of objects · 1–100 itemsRequiredFields
messageIdstringRequiredsourceIdstring · starts with src_Required
Responses
- 200
The messages as they are after the move
Response fields
messageslist of objectsRequiredFields
messageIdstringRequiredsourceobjectRequiredThe source a message belongs to. Inbox is the built-in
systemsource; sources you create arecustom.Fields
kindstringRequiredsystemcustomidstring · starts with src_RequiredlabelstringRequired
titlestring · can be nullRequiredpreviewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.bodystring · can be nullRequiredlinkstring · URL · can be nullRequiredlinkTitlestring · up to 64 characters · can be nullalertstring · can be nullRequiredquietstandardimportantsoundstring · can be nullhoparcadeclinkdropechoglowknockliftsparkleswooptickwaveThe sound the sender chose for this message. Null when it plays its source's sound or the account's.
firstReceivedAtstring · date and timeRequiredWhen Ripple first received the message. It never changes.
receivedAtstring · date and timeRequiredThe message's date in lists. It starts as the time the message arrived and moves forward when the message comes back from a snooze.
updatedAtstring · date and timeRequiredrevisioninteger · at least 1RequiredGoes up each time the message changes. Send it as
expectedRevisionto act only on the version you read.snoozeobject · can be nullRequiredFields
snoozeIdstringRequiredreturnAtstring · date and timeRequiredtimeZonestringRequiredalertstringRequiredquietstandardimportantgenerationinteger · at least 1RequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
readAtstring · date and time · can be nullarchivedAtstring · date and time · can be nullthreadobjectFields
idstring · starts with thr_Requiredtitlestring · up to 120 charactersRequiredlatestMessageIdstringRequiredlatestMessageNumberinteger · at least 1RequiredmessageCountinteger · at least 1RequiredlastSeenMessageNumberinteger · at least 0RequiredunseenMessageCountinteger · at least 0RequiredfirstUnseenMessageNumberinteger · at least 1 · can be nullRequiredmanuallyUnreadbooleanRequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
previousMessageobjectFor a message in a thread, the message before it, as in message lists. Present only when the thread has an earlier message.
Fields
messageIdstringRequiredmessageNumberinteger · at least 1RequiredpreviewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.alertstring · can be nullRequiredquietstandardimportant
sourceStatestringactivetrashdeletedsourceDeletedAtstring · date and time · can be null
resultslist of objectsRequiredFields
messageIdstringRequiredfromSourceobjectRequiredThe source a message belongs to. Inbox is the built-in
systemsource; sources you create arecustom.Fields
kindstringRequiredsystemcustomidstring · starts with src_RequiredlabelstringRequired
toSourceobjectRequiredThe source a message belongs to. Inbox is the built-in
systemsource; sources you create arecustom.Fields
kindstringRequiredsystemcustomidstring · starts with src_RequiredlabelstringRequired
changedbooleanRequired
changedCountinteger · at least 0RequiredmutationAtstring · date and timeRequired
- 400
movesis missing or malformed, names a message twice, or has more than 100 entries (invalid_message_moves) - 401
The request has no valid API token or sign-in (
unauthorized) - 403
The API token is read-only (
read_only_token), so it can only list, read and search. - 404
A selected message or target source was not found (
not_found) - 409
A selected message is in Trash (
message_deleted) or belongs to a thread (thread_move_not_supported) - 503
Ripple is busy right now (
database_busy). Wait the number of seconds inRetry-After, then try again.
curl https://api.ripplepush.com/v1/messages/move \
-H "Authorization: Bearer $RIPPLE_KEY" \
-H "Content-Type: application/json" \
-d '{
"moves": [
{
"messageId": "msg_0575320416fe4544b646044392618fbd",
"sourceId": "src_65ab57d4851f4e828dad9e4c29ea0f48"
}
]
}'const response = await fetch("https://api.ripplepush.com/v1/messages/move", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RIPPLE_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
moves: [
{
messageId: "msg_0575320416fe4544b646044392618fbd",
sourceId: "src_65ab57d4851f4e828dad9e4c29ea0f48",
},
],
}),
});
console.log(response.status, await response.text());import json
import os
import urllib.request
request = urllib.request.Request(
"https://api.ripplepush.com/v1/messages/move",
data=json.dumps({
"moves": [
{
"messageId": "msg_0575320416fe4544b646044392618fbd",
"sourceId": "src_65ab57d4851f4e828dad9e4c29ea0f48",
},
],
}).encode(),
headers={
"Authorization": "Bearer " + os.environ["RIPPLE_KEY"],
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(request, timeout=30) as response:
print(response.status, response.read().decode()){
"messages": [
{
"messageId": "msg_0575320416fe4544b646044392618fbd",
"source": {
"kind": "custom",
"id": "src_65ab57d4851f4e828dad9e4c29ea0f48",
"label": "Finished builds"
},
"title": "Deployment complete",
"preview": "Version 1.2 is ready.",
"body": "Version 1.2 is ready.",
"link": null,
"linkTitle": null,
"alert": "quiet",
"firstReceivedAt": "2026-10-03T13:31:07.201Z",
"receivedAt": "2026-10-03T13:31:07.201Z",
"updatedAt": "2026-10-03T13:31:07.201Z",
"readAt": null,
"archivedAt": null,
"revision": 8,
"snooze": null,
"sourceDeletedAt": null,
"sourceState": "active"
}
],
"results": [
{
"messageId": "msg_0575320416fe4544b646044392618fbd",
"fromSource": {
"kind": "custom",
"id": "src_aa960551ca5b4ec68f82747db1f75a3b",
"label": "Deploy notifications"
},
"toSource": {
"kind": "custom",
"id": "src_65ab57d4851f4e828dad9e4c29ea0f48",
"label": "Finished builds"
},
"changed": true
}
],
"changedCount": 1,
"mutationAt": "2026-10-03T13:31:07.445Z"
}{
"code": "unauthorized",
"message": "Send a valid API token, or sign in."
}{
"code": "read_only_token",
"message": "This API token can only read."
}The messages as they are after the move
Snooze a message
PUT/v1/messages/{messageId}/snooze
Takes a message out of All Messages until returnAt, then brings it back. If the message is already snoozed, this replaces its snooze. You can also choose the time zone, the alert it comes back with, and an expectedRevision so the snooze applies only to the version you read.
AuthAPI token
Path parameters
messageIdstringRequired
Body
returnAtstring · date and timeRequiredThe exact time the message comes back, such as
2026-10-03T14:31:07Z. Ripple doesn't read phrases liketomorrow at 9, so work out the time before you send it.timeZonestring · 1–255 charactersAn IANA time zone, such as
America/, kept for showing and editing the snooze. If you leave it out, a replaced snooze keeps its zone. Otherwise Ripple uses the account's time zone if it has one (it keeps one while quiet hours are on), orNew_York UTC.alertstringquietstandardimportantThe alert the message comes back with. If you leave it out, a replaced snooze keeps its alert. Otherwise an
importantmessage comes backimportantand any other comes backstandard.expectedRevisioninteger · at least 1The message's
revisionwhen you read it. If the message has changed since, the request fails with409 message_changed. Leave it out to snooze the message as it is now.
Responses
- 200
The message with its snooze
Response fields
messageobjectRequiredFields
messageIdstringRequiredsourceobjectRequiredThe source a message belongs to. Inbox is the built-in
systemsource; sources you create arecustom.Fields
kindstringRequiredsystemcustomidstring · starts with src_RequiredlabelstringRequired
titlestring · can be nullRequiredpreviewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.bodystring · can be nullRequiredlinkstring · URL · can be nullRequiredlinkTitlestring · up to 64 characters · can be nullalertstring · can be nullRequiredquietstandardimportantsoundstring · can be nullhoparcadeclinkdropechoglowknockliftsparkleswooptickwaveThe sound the sender chose for this message. Null when it plays its source's sound or the account's.
firstReceivedAtstring · date and timeRequiredWhen Ripple first received the message. It never changes.
receivedAtstring · date and timeRequiredThe message's date in lists. It starts as the time the message arrived and moves forward when the message comes back from a snooze.
updatedAtstring · date and timeRequiredrevisioninteger · at least 1RequiredGoes up each time the message changes. Send it as
expectedRevisionto act only on the version you read.snoozeobject · can be nullRequiredFields
snoozeIdstringRequiredreturnAtstring · date and timeRequiredtimeZonestringRequiredalertstringRequiredquietstandardimportantgenerationinteger · at least 1RequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
readAtstring · date and time · can be nullarchivedAtstring · date and time · can be nullthreadobjectFields
idstring · starts with thr_Requiredtitlestring · up to 120 charactersRequiredlatestMessageIdstringRequiredlatestMessageNumberinteger · at least 1RequiredmessageCountinteger · at least 1RequiredlastSeenMessageNumberinteger · at least 0RequiredunseenMessageCountinteger · at least 0RequiredfirstUnseenMessageNumberinteger · at least 1 · can be nullRequiredmanuallyUnreadbooleanRequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
previousMessageobjectFor a message in a thread, the message before it, as in message lists. Present only when the thread has an earlier message.
Fields
messageIdstringRequiredmessageNumberinteger · at least 1RequiredpreviewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.alertstring · can be nullRequiredquietstandardimportant
sourceStatestringactivetrashdeletedsourceDeletedAtstring · date and time · can be null
- 400
The request is malformed (
invalid_snooze_request), or its time zone (invalid_time_zone) or alert (invalid_alert) is not valid - 401
The request has no valid API token or sign-in (
unauthorized) - 403
The API token is read-only (
read_only_token), so it can only list, read and search. - 404
The message was not found (
not_found) - 409
The message has changed since
expectedRevision(message_changed, with the message as it is now incurrent), is in Trash (message_deleted) or is archived (message_archived) - 422
returnAthas passed (snooze_time_in_past) or is more than 365 days away (snooze_time_too_far) - 503
Ripple is busy right now (
database_busy). Wait the number of seconds inRetry-After, then try again.
curl -X PUT https://api.ripplepush.com/v1/messages/MESSAGE_ID/snooze \
-H "Authorization: Bearer $RIPPLE_KEY" \
-H "Content-Type: application/json" \
-d '{
"returnAt": "2026-10-03T14:31:07.364Z"
}'const response = await fetch("https://api.ripplepush.com/v1/messages/MESSAGE_ID/snooze", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.RIPPLE_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
returnAt: "2026-10-03T14:31:07.364Z",
}),
});
console.log(response.status, await response.text());import json
import os
import urllib.request
request = urllib.request.Request(
"https://api.ripplepush.com/v1/messages/MESSAGE_ID/snooze",
data=json.dumps({
"returnAt": "2026-10-03T14:31:07.364Z",
}).encode(),
headers={
"Authorization": "Bearer " + os.environ["RIPPLE_KEY"],
"Content-Type": "application/json",
},
method="PUT",
)
with urllib.request.urlopen(request, timeout=30) as response:
print(response.status, response.read().decode()){
"message": {
"messageId": "msg_0575320416fe4544b646044392618fbd",
"source": {
"kind": "custom",
"id": "src_aa960551ca5b4ec68f82747db1f75a3b",
"label": "Deploy notifications"
},
"title": "Deployment complete",
"preview": "Version 1.2 is ready.",
"link": null,
"linkTitle": null,
"alert": "quiet",
"firstReceivedAt": "2026-10-03T13:31:07.201Z",
"receivedAt": "2026-10-03T13:31:07.201Z",
"updatedAt": "2026-10-03T13:31:07.201Z",
"readAt": null,
"archivedAt": null,
"revision": 6,
"snooze": {
"snoozeId": "snz_b4b4e9c6740d4c78b4b73e6115c1c335",
"returnAt": "2026-10-03T14:31:07.364Z",
"timeZone": "UTC",
"alert": "quiet",
"generation": 1,
"createdAt": "2026-10-03T13:31:07.416Z",
"updatedAt": "2026-10-03T13:31:07.416Z"
},
"body": "Version 1.2 is ready."
}
}{
"code": "unauthorized",
"message": "Send a valid API token, or sign in."
}{
"code": "read_only_token",
"message": "This API token can only read."
}The message with its snooze
Cancel a snooze
DELETE/v1/messages/{messageId}/snooze
Cancels a message's snooze. Its read state and alert stay as they are, and no notification is sent. Add expectedRevision to cancel only if the message hasn't changed since you read it. Send no body.
AuthAPI token
Path parameters
messageIdstringRequired
Query parameters
expectedRevisioninteger · at least 1The message's
revisionwhen you read it. If the message has changed since, the request fails with409 message_changed.
Responses
- 200
The message, now without a snooze. You get this even if no snooze was left to cancel.
Response fields
messageobjectRequiredFields
messageIdstringRequiredsourceobjectRequiredThe source a message belongs to. Inbox is the built-in
systemsource; sources you create arecustom.Fields
kindstringRequiredsystemcustomidstring · starts with src_RequiredlabelstringRequired
titlestring · can be nullRequiredpreviewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.bodystring · can be nullRequiredlinkstring · URL · can be nullRequiredlinkTitlestring · up to 64 characters · can be nullalertstring · can be nullRequiredquietstandardimportantsoundstring · can be nullhoparcadeclinkdropechoglowknockliftsparkleswooptickwaveThe sound the sender chose for this message. Null when it plays its source's sound or the account's.
firstReceivedAtstring · date and timeRequiredWhen Ripple first received the message. It never changes.
receivedAtstring · date and timeRequiredThe message's date in lists. It starts as the time the message arrived and moves forward when the message comes back from a snooze.
updatedAtstring · date and timeRequiredrevisioninteger · at least 1RequiredGoes up each time the message changes. Send it as
expectedRevisionto act only on the version you read.snoozeobject · can be nullRequiredFields
snoozeIdstringRequiredreturnAtstring · date and timeRequiredtimeZonestringRequiredalertstringRequiredquietstandardimportantgenerationinteger · at least 1RequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
readAtstring · date and time · can be nullarchivedAtstring · date and time · can be nullthreadobjectFields
idstring · starts with thr_Requiredtitlestring · up to 120 charactersRequiredlatestMessageIdstringRequiredlatestMessageNumberinteger · at least 1RequiredmessageCountinteger · at least 1RequiredlastSeenMessageNumberinteger · at least 0RequiredunseenMessageCountinteger · at least 0RequiredfirstUnseenMessageNumberinteger · at least 1 · can be nullRequiredmanuallyUnreadbooleanRequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
previousMessageobjectFor a message in a thread, the message before it, as in message lists. Present only when the thread has an earlier message.
Fields
messageIdstringRequiredmessageNumberinteger · at least 1RequiredpreviewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.alertstring · can be nullRequiredquietstandardimportant
sourceStatestringactivetrashdeletedsourceDeletedAtstring · date and time · can be null
- 400
expectedRevisionisn't a positive whole number (invalid_snooze_request), or the request has a body (unexpected_body) - 401
The request has no valid API token or sign-in (
unauthorized) - 403
The API token is read-only (
read_only_token), so it can only list, read and search. - 404
The message was not found (
not_found) - 409
The message has changed since
expectedRevision(message_changed, with the message as it is now incurrent), is in Trash (message_deleted) or is archived (message_archived) - 503
Ripple is busy right now (
database_busy). Wait the number of seconds inRetry-After, then try again.
curl -X DELETE https://api.ripplepush.com/v1/messages/MESSAGE_ID/snooze \
-H "Authorization: Bearer $RIPPLE_KEY"const response = await fetch("https://api.ripplepush.com/v1/messages/MESSAGE_ID/snooze", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.RIPPLE_KEY}`,
},
});
console.log(response.status, await response.text());import os
import urllib.request
request = urllib.request.Request(
"https://api.ripplepush.com/v1/messages/MESSAGE_ID/snooze",
headers={
"Authorization": "Bearer " + os.environ["RIPPLE_KEY"],
},
method="DELETE",
)
with urllib.request.urlopen(request, timeout=30) as response:
print(response.status, response.read().decode()){
"message": {
"messageId": "msg_0575320416fe4544b646044392618fbd",
"source": {
"kind": "custom",
"id": "src_aa960551ca5b4ec68f82747db1f75a3b",
"label": "Deploy notifications"
},
"title": "Deployment complete",
"preview": "Version 1.2 is ready.",
"link": null,
"linkTitle": null,
"alert": "quiet",
"firstReceivedAt": "2026-10-03T13:31:07.201Z",
"receivedAt": "2026-10-03T13:31:07.201Z",
"updatedAt": "2026-10-03T13:31:07.201Z",
"readAt": null,
"archivedAt": null,
"revision": 7,
"snooze": null,
"body": "Version 1.2 is ready."
}
}{
"code": "unauthorized",
"message": "Send a valid API token, or sign in."
}{
"code": "read_only_token",
"message": "This API token can only read."
}The message, now without a snooze. You get this even if no snooze was left to cancel.
Read a thread
GET/v1/threads/{threadId}
Returns a thread, its latest message and a page of its messages, newest first, with what happened to them in activity. For the next page, pass nextCursor as cursor. From GET /v1/, pass its timelineCursor to continue past its timeline, or its requestedMessage.cursor for the page that ends at that message. A cursor from another thread returns invalid_cursor.
When two accounts are joined, some thread IDs are replaced. An old ID still finds the thread that replaced it, and its cursors work with either ID.
AuthAPI token
Path parameters
threadIdstring · starts with thr_Required
Query parameters
limitinteger · 1–100 · default 50The most items to return in one page.
cursorstringThe
nextCursorfrom the previous page. Keep the other filters the same.
Responses
- 200
The thread and one page of its messages
Response fields
threadobjectRequiredFields
idstring · starts with thr_Requiredtitlestring · up to 120 charactersRequiredlatestMessageIdstringRequiredlatestMessageNumberinteger · at least 1RequiredmessageCountinteger · at least 1RequiredlastSeenMessageNumberinteger · at least 0RequiredunseenMessageCountinteger · at least 0RequiredfirstUnseenMessageNumberinteger · at least 1 · can be nullRequiredmanuallyUnreadbooleanRequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
latestMessageobjectRequiredFields
messageIdstringRequiredsourceobjectRequiredThe source a message belongs to. Inbox is the built-in
systemsource; sources you create arecustom.Fields
kindstringRequiredsystemcustomidstring · starts with src_RequiredlabelstringRequired
titlestring · can be nullRequiredpreviewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.bodystring · can be nullRequiredlinkstring · URL · can be nullRequiredlinkTitlestring · up to 64 characters · can be nullalertstring · can be nullRequiredquietstandardimportantsoundstring · can be nullhoparcadeclinkdropechoglowknockliftsparkleswooptickwaveThe sound the sender chose for this message. Null when it plays its source's sound or the account's.
firstReceivedAtstring · date and timeRequiredWhen Ripple first received the message. It never changes.
receivedAtstring · date and timeRequiredThe message's date in lists. It starts as the time the message arrived and moves forward when the message comes back from a snooze.
updatedAtstring · date and timeRequiredrevisioninteger · at least 1RequiredGoes up each time the message changes. Send it as
expectedRevisionto act only on the version you read.snoozeobject · can be nullRequiredFields
snoozeIdstringRequiredreturnAtstring · date and timeRequiredtimeZonestringRequiredalertstringRequiredquietstandardimportantgenerationinteger · at least 1RequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
readAtstring · date and time · can be nullarchivedAtstring · date and time · can be nullthreadobjectFields
idstring · starts with thr_Requiredtitlestring · up to 120 charactersRequiredlatestMessageIdstringRequiredlatestMessageNumberinteger · at least 1RequiredmessageCountinteger · at least 1RequiredlastSeenMessageNumberinteger · at least 0RequiredunseenMessageCountinteger · at least 0RequiredfirstUnseenMessageNumberinteger · at least 1 · can be nullRequiredmanuallyUnreadbooleanRequiredcreatedAtstring · date and timeRequiredupdatedAtstring · date and timeRequired
previousMessageobjectFor a message in a thread, the message before it, as in message lists. Present only when the thread has an earlier message.
Fields
messageIdstringRequiredmessageNumberinteger · at least 1RequiredpreviewstringRequiredThe body as one line of plain text, without Markdown syntax or HTML tags, after a first line that repeats the title. Past 280 UTF-16 code units it ends with
…. Empty when nothing follows the title.alertstring · can be nullRequiredquietstandardimportant
sourceStatestringRequiredactivetrashdeletedsourceDeletedAtstring · date and time · can be nullRequired
messageslist of objectsRequiredFields
messageIdstringRequiredmessageNumberinteger · at least 1RequiredsourceobjectRequiredThe source a message belongs to. Inbox is the built-in
systemsource; sources you create arecustom.Fields
kindstringRequiredsystemcustomidstring · starts with src_RequiredlabelstringRequired
titlestring · can be nullRequiredbodystring · can be nullRequiredlinkstring · URL · can be nullRequiredlinkTitlestring · up to 64 characters · can be nullalertstring · can be nullRequiredquietstandardimportantsoundstring · can be nullhoparcadeclinkdropechoglowknockliftsparkleswooptickwaveThe sound the sender chose for this message. Null when it plays its source's sound or the account's.
receivedAtstring · date and timeRequired
activitylist of objectsRequiredWhat happened to the messages on this page, such as archiving or snoozing. Marking them read or unread isn't included.
Fields
eventIdstringRequiredeventTypestringRequiredarchivedunarchivedsnoozedsnooze_changedsnooze_canceledalert_changedcreatedAtstring · date and timeRequiredsourceobjectRequiredThe source a message belongs to. Inbox is the built-in
systemsource; sources you create arecustom.Fields
kindstringRequiredsystemcustomidstring · starts with src_RequiredlabelstringRequired
messageIdstringRequiredmessageNumberinteger · at least 1Requiredmetadataobject · can be nullRequiredFields
threadTitlestring · can be nullThe thread's title when the message arrived, or
nullfor a message that isn't in a thread.snoozeIdstringgenerationinteger · at least 1returnAtstring · date and timetimeZonestringpreviousReturnAtstring · date and timepreviousTimeZonestringalertstringquietstandardimportantfromstring · can be nullquietstandardimportanttostringquietstandardimportantfromSourceobjectThe source a message belongs to. Inbox is the built-in
systemsource; sources you create arecustom.Fields
kindstringRequiredsystemcustomidstring · starts with src_RequiredlabelstringRequired
toSourceobjectThe source a message belongs to. Inbox is the built-in
systemsource; sources you create arecustom.Fields
kindstringRequiredsystemcustomidstring · starts with src_RequiredlabelstringRequired
nextCursorstring · can be nullRequiredPass this as
cursorfor older messages.nullon the last page.
- 400
The cursor came from a different list (
invalid_cursor) - 401
The request has no valid API token or sign-in (
unauthorized) - 404
The thread was not found (
not_found) - 503
Ripple is busy right now (
database_busy). Wait the number of seconds inRetry-After, then try again.
curl https://api.ripplepush.com/v1/threads/thr_abc123 \
-H "Authorization: Bearer $RIPPLE_KEY"const response = await fetch("https://api.ripplepush.com/v1/threads/thr_abc123", {
headers: {
Authorization: `Bearer ${process.env.RIPPLE_KEY}`,
},
});
console.log(response.status, await response.text());import os
import urllib.request
request = urllib.request.Request(
"https://api.ripplepush.com/v1/threads/thr_abc123",
headers={
"Authorization": "Bearer " + os.environ["RIPPLE_KEY"],
},
method="GET",
)
with urllib.request.urlopen(request, timeout=30) as response:
print(response.status, response.read().decode()){
"thread": {
"id": "thr_e91d5ec03ea9489d89a302645ccca63b",
"title": "Release progress",
"latestMessageId": "msg_2fe56d9f692c4febb604bb2c2c884ae7",
"latestMessageNumber": 1,
"messageCount": 1,
"lastSeenMessageNumber": 0,
"unseenMessageCount": 1,
"firstUnseenMessageNumber": 1,
"manuallyUnread": false,
"createdAt": "2026-10-03T13:31:07.219Z",
"updatedAt": "2026-10-03T13:31:07.219Z"
},
"latestMessage": {
"messageId": "msg_2fe56d9f692c4febb604bb2c2c884ae7",
"source": {
"kind": "custom",
"id": "src_aa960551ca5b4ec68f82747db1f75a3b",
"label": "Deploy notifications"
},
"title": null,
"preview": "Build completed.",
"body": "Build completed.",
"link": null,
"linkTitle": null,
"alert": "standard",
"firstReceivedAt": "2026-10-03T13:31:07.219Z",
"receivedAt": "2026-10-03T13:31:07.219Z",
"updatedAt": "2026-10-03T13:31:07.219Z",
"readAt": null,
"archivedAt": null,
"revision": 1,
"snooze": null,
"thread": {
"id": "thr_e91d5ec03ea9489d89a302645ccca63b",
"title": "Release progress",
"latestMessageId": "msg_2fe56d9f692c4febb604bb2c2c884ae7",
"latestMessageNumber": 1,
"messageCount": 1,
"lastSeenMessageNumber": 0,
"unseenMessageCount": 1,
"firstUnseenMessageNumber": 1,
"manuallyUnread": false,
"createdAt": "2026-10-03T13:31:07.219Z",
"updatedAt": "2026-10-03T13:31:07.219Z"
},
"sourceDeletedAt": null,
"sourceState": "active"
},
"messages": [
{
"messageId": "msg_2fe56d9f692c4febb604bb2c2c884ae7",
"messageNumber": 1,
"source": {
"kind": "custom",
"id": "src_aa960551ca5b4ec68f82747db1f75a3b",
"label": "Deploy notifications"
},
"title": null,
"body": "Build completed.",
"link": null,
"linkTitle": null,
"alert": "standard",
"receivedAt": "2026-10-03T13:31:07.219Z"
}
],
"activity": [],
"nextCursor": null
}{
"code": "unauthorized",
"message": "Send a valid API token, or sign in."
}The thread and one page of its messages