curl --request POST \
--url https://api.reflecto.dev/v1/send/{token} \
--header 'Content-Type: application/json' \
--data '
{
"message": "Backup finished in 12m",
"title": "Backup",
"priority": "default",
"tags": [
"ops"
],
"url": "https://example.com/backup/123",
"url_title": "View report",
"actions": [
{
"label": "Acknowledge",
"url": "https://example.com/ack"
}
],
"device": "all",
"markdown": false,
"ttl": 259200
}
'import requests
url = "https://api.reflecto.dev/v1/send/{token}"
payload = {
"message": "Backup finished in 12m",
"title": "Backup",
"priority": "default",
"tags": ["ops"],
"url": "https://example.com/backup/123",
"url_title": "View report",
"actions": [
{
"label": "Acknowledge",
"url": "https://example.com/ack"
}
],
"device": "all",
"markdown": False,
"ttl": 259200
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
message: 'Backup finished in 12m',
title: 'Backup',
priority: 'default',
tags: ['ops'],
url: 'https://example.com/backup/123',
url_title: 'View report',
actions: [{label: 'Acknowledge', url: 'https://example.com/ack'}],
device: 'all',
markdown: false,
ttl: 259200
})
};
fetch('https://api.reflecto.dev/v1/send/{token}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.reflecto.dev/v1/send/{token}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'message' => 'Backup finished in 12m',
'title' => 'Backup',
'priority' => 'default',
'tags' => [
'ops'
],
'url' => 'https://example.com/backup/123',
'url_title' => 'View report',
'actions' => [
[
'label' => 'Acknowledge',
'url' => 'https://example.com/ack'
]
],
'device' => 'all',
'markdown' => false,
'ttl' => 259200
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.reflecto.dev/v1/send/{token}"
payload := strings.NewReader("{\n \"message\": \"Backup finished in 12m\",\n \"title\": \"Backup\",\n \"priority\": \"default\",\n \"tags\": [\n \"ops\"\n ],\n \"url\": \"https://example.com/backup/123\",\n \"url_title\": \"View report\",\n \"actions\": [\n {\n \"label\": \"Acknowledge\",\n \"url\": \"https://example.com/ack\"\n }\n ],\n \"device\": \"all\",\n \"markdown\": false,\n \"ttl\": 259200\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.reflecto.dev/v1/send/{token}")
.header("Content-Type", "application/json")
.body("{\n \"message\": \"Backup finished in 12m\",\n \"title\": \"Backup\",\n \"priority\": \"default\",\n \"tags\": [\n \"ops\"\n ],\n \"url\": \"https://example.com/backup/123\",\n \"url_title\": \"View report\",\n \"actions\": [\n {\n \"label\": \"Acknowledge\",\n \"url\": \"https://example.com/ack\"\n }\n ],\n \"device\": \"all\",\n \"markdown\": false,\n \"ttl\": 259200\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.reflecto.dev/v1/send/{token}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"message\": \"Backup finished in 12m\",\n \"title\": \"Backup\",\n \"priority\": \"default\",\n \"tags\": [\n \"ops\"\n ],\n \"url\": \"https://example.com/backup/123\",\n \"url_title\": \"View report\",\n \"actions\": [\n {\n \"label\": \"Acknowledge\",\n \"url\": \"https://example.com/ack\"\n }\n ],\n \"device\": \"all\",\n \"markdown\": false,\n \"ttl\": 259200\n}"
response = http.request(request)
puts response.read_body{
"id": "msg_3f6e2a7c9b1d4f8e8a5b6c1d2e3f4a5b",
"delivered_to": [
{
"device_id": "dev_9af3",
"type": "android",
"delivery": "push"
}
],
"warnings": [
"<string>"
]
}Send via capability URL (path-token alias)
Convenience alias for POST /v1/send where the bearer token is the last path segment instead of the Authorization header. Useful in environments that cannot set custom headers (basic IoT devices, simple shell pipelines).
Path-token requests have their URL redacted before any access log write. The header form is still the canonical/preferred way to call the API — path-tokens are easier to leak via shell history, screenshots, and referrers.
curl --request POST \
--url https://api.reflecto.dev/v1/send/{token} \
--header 'Content-Type: application/json' \
--data '
{
"message": "Backup finished in 12m",
"title": "Backup",
"priority": "default",
"tags": [
"ops"
],
"url": "https://example.com/backup/123",
"url_title": "View report",
"actions": [
{
"label": "Acknowledge",
"url": "https://example.com/ack"
}
],
"device": "all",
"markdown": false,
"ttl": 259200
}
'import requests
url = "https://api.reflecto.dev/v1/send/{token}"
payload = {
"message": "Backup finished in 12m",
"title": "Backup",
"priority": "default",
"tags": ["ops"],
"url": "https://example.com/backup/123",
"url_title": "View report",
"actions": [
{
"label": "Acknowledge",
"url": "https://example.com/ack"
}
],
"device": "all",
"markdown": False,
"ttl": 259200
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
message: 'Backup finished in 12m',
title: 'Backup',
priority: 'default',
tags: ['ops'],
url: 'https://example.com/backup/123',
url_title: 'View report',
actions: [{label: 'Acknowledge', url: 'https://example.com/ack'}],
device: 'all',
markdown: false,
ttl: 259200
})
};
fetch('https://api.reflecto.dev/v1/send/{token}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.reflecto.dev/v1/send/{token}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'message' => 'Backup finished in 12m',
'title' => 'Backup',
'priority' => 'default',
'tags' => [
'ops'
],
'url' => 'https://example.com/backup/123',
'url_title' => 'View report',
'actions' => [
[
'label' => 'Acknowledge',
'url' => 'https://example.com/ack'
]
],
'device' => 'all',
'markdown' => false,
'ttl' => 259200
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.reflecto.dev/v1/send/{token}"
payload := strings.NewReader("{\n \"message\": \"Backup finished in 12m\",\n \"title\": \"Backup\",\n \"priority\": \"default\",\n \"tags\": [\n \"ops\"\n ],\n \"url\": \"https://example.com/backup/123\",\n \"url_title\": \"View report\",\n \"actions\": [\n {\n \"label\": \"Acknowledge\",\n \"url\": \"https://example.com/ack\"\n }\n ],\n \"device\": \"all\",\n \"markdown\": false,\n \"ttl\": 259200\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.reflecto.dev/v1/send/{token}")
.header("Content-Type", "application/json")
.body("{\n \"message\": \"Backup finished in 12m\",\n \"title\": \"Backup\",\n \"priority\": \"default\",\n \"tags\": [\n \"ops\"\n ],\n \"url\": \"https://example.com/backup/123\",\n \"url_title\": \"View report\",\n \"actions\": [\n {\n \"label\": \"Acknowledge\",\n \"url\": \"https://example.com/ack\"\n }\n ],\n \"device\": \"all\",\n \"markdown\": false,\n \"ttl\": 259200\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.reflecto.dev/v1/send/{token}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"message\": \"Backup finished in 12m\",\n \"title\": \"Backup\",\n \"priority\": \"default\",\n \"tags\": [\n \"ops\"\n ],\n \"url\": \"https://example.com/backup/123\",\n \"url_title\": \"View report\",\n \"actions\": [\n {\n \"label\": \"Acknowledge\",\n \"url\": \"https://example.com/ack\"\n }\n ],\n \"device\": \"all\",\n \"markdown\": false,\n \"ttl\": 259200\n}"
response = http.request(request)
puts response.read_body{
"id": "msg_3f6e2a7c9b1d4f8e8a5b6c1d2e3f4a5b",
"delivered_to": [
{
"device_id": "dev_9af3",
"type": "android",
"delivery": "push"
}
],
"warnings": [
"<string>"
]
}Path Parameters
Reflecto sender token (rfk_live_… or rfk_test_…).
^rfk_(live|test)_[A-Za-z0-9]{32}$"rfk_live_aB3xQ7mN9pK2vR5tY8uW4sZ1cE6dF0gH"
Body
Canonical send payload. Form-encoded and text/plain bodies are documented in the guides — the server normalises them to this shape.
Notification body as plain text — write quotes and newlines literally, never JSON-escape them (no " or \n sequences; the JSON layer handles encoding). Required. 1–1500 bytes (UTF-8). maxLength is omitted from the schema because JSON Schema measures characters, not bytes — server enforces a byte cap to keep the 2 KB envelope budget honest across CJK / emoji content.
1"Backup finished in 12m"
Headline rendered above the body. 1–100 bytes (UTF-8). Schema omits maxLength because JSON Schema measures characters, not bytes.
1"Backup"
Notification priority. min/low use normal FCM; rest use high.
min, low, default, high, urgent "default"
Up to 5 tags rendered as labels. Oversize / overcount entries are truncated with a warning rather than rejected — maxItems is deliberately omitted so SDK generators don't client-side-reject valid-but-overcount inputs that the server will accept and trim.
Tag string. Oversize / overcount entries are truncated with a warning rather than rejected.
Clickable URL attached to the notification. http(s) only, ≤ 512 bytes.
"https://example.com/backup/123"
Display text for url. ≤ 32 bytes (UTF-8). Schema omits maxLength because JSON Schema measures characters, not bytes.
"View report"
Up to 3 action buttons. Each button opens its url when tapped. Overcount entries are truncated with a warning — maxItems is deliberately omitted so SDK generators don't client-side-reject.
Show child attributes
Show child attributes
Target device(s). all (default), mobile (alias phone), pc (every computer — browser extension, web app, CLI and desktop app), desktop (the native desktop app only), a comma-separated list of device labels, or an exact device id. Labels are not unique — one shared by two devices delivers to both, so pass an id to reach exactly one. all, mobile, phone, pc and desktop are reserved: a device you have NAMED after one of them is not reachable by that name, only by its id. An unknown label becomes a warning and falls back to delivering everywhere; a reserved word that matches none of your devices delivers to nobody and says so in warnings.
256"all"
Render the body as Markdown on the extension. Ignored on Android in MVP.
false
Seconds the message stays in the per-device queue if not delivered live. Out-of-range values are clamped to the [0, 259200] range (72 hours, matching the Redis queue TTL) and a warning is returned.
0 <= x <= 259200259200