Send
Post a message with a send key or an API token.
Send a message
POST/v1/send
Sends a message to one of your sources. Use the source's send key, in the Authorization header or as ?key= in the address; the key decides where the message goes. An API token can send too, to the source you name in source or to Inbox.
A plain-text body becomes the message text. Send JSON when you want a title, a thread, a link, an alert or a sound. Use thread to collect related updates under one title within the source. Leave out alert to use the source's default alert, or standard if it has none. Leave out sound to play the source's sound, or the account's if the source has none.
AuthSend keyorKey in the addressorAPI token
Headers
Idempotency-KeystringA key you choose so that retrying a send can't create a duplicate. For 24 hours, the same key with the same message to the same source returns the original response instead of saving a new message. Ripple compares messages after its usual cleanup, such as trimming whitespace. The same key with a different message returns
409 idempotency_mismatch. After 24 hours the key can save a new message. A blank key is ignored.
Body
titlestringA title shown above the message in Ripple and in notifications. Without one, the body's first line with text becomes the title, as plain text and shortened to 120 characters at a word with an ellipsis. Leave it out when you use
thread. A title longer than 120 characters is shortened without splitting a character or emoji, and the response includes atitle_truncatedwarning.bodystring · up to 64,000 charactersThe main text of the message, shown in full when someone opens it. Up to 64,000 characters.
linkstring · URL · up to 2,048 characters · starts with https://An
https:/link to open from the message. It can be up to 2,048 characters, both as you send it and after Ripple URL-encodes it./ linkTitlestring · up to 64 charactersA label for the link's button, up to 64 characters. Needs
link.alertstringquietstandardimportantHow strongly the message alerts. Leave it out to use the source's default alert, or
standardif the source has none.soundstringhoparcadeclinkdropechoglowknockliftsparkleswooptickwaveThe sound this message plays when it alerts with a sound. Leave it out to play the source's sound, or the account's if the source has none. A sound never makes a message alert more strongly.
sourcestring · starts with src_The ID of the source to send to when you use an API token. Leave it out to send to Inbox. A send key already decides its source, so with a key, leave this out or give that same source's ID.
threadstring · 1–120 charactersCollects this message with earlier ones under one title, such as
Production deploy. Messages with the samethreadin the same source join one thread, shown under this title. Ripple trims the ends, turns runs of whitespace into one space and ignores case when matching, and keeps the title as it was first sent. Punctuation and wording still count, soDeployandDeploy!are different threads.
Or send text/plain
The message text, with leading and trailing whitespace trimmed. A form-encoded body (application/, which curl -d sends by default) is read the same way, but a JSON object sent as form data returns 400 invalid_payload.
Responses
- 202
Ripple saved the message to its source. This doesn't mean a device has shown it yet.
Response fields
statusstringRequiredacceptedmessageIdstringRequiredsourceobjectRequiredThe source a message belongs to. Inbox is the built-in
systemsource; sources you create arecustom.Fields
kindstringRequiredsystemcustomidstring · starts with src_RequiredlabelstringRequired
receivedAtstring · date and timeRequiredwarningslist of objectsPresent when Ripple saved the message but changed something in it, such as shortening a long title.
Fields
codestringRequiredtitle_truncatedmessagestringRequired
threadobjectFields
idstring · starts with thr_Requiredtitlestring · up to 120 charactersRequiredcreatedbooleanRequiredtruewhen this send started a new thread, because the source had no thread with this title outside Trash. A replayed send returns the original value.messageNumberinteger · at least 1RequiredThis message's place in the thread, counting from 1 in the order Ripple received the messages.
- 400
Ripple couldn't take the message. The
sourceisn't this key's source or isn't on this account (invalid_source). The message has no title or body, or a message in a thread has no body (missing_content). It has a field Ripple doesn't know (unsupported_field) or a field with a value that isn't allowed (invalid_payload), or its JSON can't be parsed (invalid_json). - 401
The key isn't valid, an API token was put in the address, or a key was sent in both the header and the address (
invalid_credential) - 403
The source is disabled (
source_disabled) or deleted (source_deleted), or the API token is read-only (read_only_token) - 409
This
Idempotency-Keywas already used for a different message (idempotency_mismatch) - 413
The request is larger than Ripple accepts (
payload_too_large) - 415
The content type isn't JSON, plain text or form data (
unsupported_media_type) - 429
Too many sends. An account can send 60 new messages in any 60 seconds (
account_rate_limited), and a source can send 20 (source_rate_limited). A source that goes over its limit is paused for 15 minutes. A replayed send with the sameIdempotency-Keydoesn't count toward either limit.Retry-Aftersays how many seconds until the account can send again, or until the source's pause ends. - 500
Something went wrong on Ripple's side (
internal_error), and the response leaves out the details. You can't tell whether the message was saved, so retry with the sameIdempotency-Keyand message: if the first try was saved, you get its original response instead of a duplicate. - 503
Ripple is busy right now (
database_busy). Wait the number of seconds inRetry-After, then retry with the sameIdempotency-Keyand message.
curl https://api.ripplepush.com/v1/send \
-H "Authorization: Bearer $RIPPLE_KEY" \
-H "Content-Type: application/json" \
-d '{
"body": "Hello from Ripple"
}'const response = await fetch("https://api.ripplepush.com/v1/send", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RIPPLE_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
body: "Hello from Ripple",
}),
});
console.log(response.status, await response.text());import json
import os
import urllib.request
request = urllib.request.Request(
"https://api.ripplepush.com/v1/send",
data=json.dumps({
"body": "Hello from Ripple",
}).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()){
"status": "accepted",
"messageId": "msg_426dd54d815b4bfaaf0940b606cd0806",
"source": {
"kind": "system",
"id": "src_9fddbdcb785848c6b10b068facd32d87",
"label": "Inbox"
},
"receivedAt": "2026-10-02T12:00:01.000Z"
}{
"code": "missing_content",
"message": "Provide at least one of title or body."
}{
"code": "source_disabled",
"message": "This source is disabled."
}Ripple saved the message to its source. This doesn't mean a device has shown it yet.