curl --request POST \
--url 'https://api.flashcat.cloud/member/notify?app_key=' \
--header 'Content-Type: application/json' \
--data '
{
"person_ids": [
5068740052131,
5068740052132
],
"subject": "Incident 20260914-1 needs your input",
"html": "<p>Can you confirm the rollback window?</p>"
}
'import requests
url = "https://api.flashcat.cloud/member/notify?app_key="
payload = {
"person_ids": [5068740052131, 5068740052132],
"subject": "Incident 20260914-1 needs your input",
"html": "<p>Can you confirm the rollback window?</p>"
}
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({
person_ids: [5068740052131, 5068740052132],
subject: 'Incident 20260914-1 needs your input',
html: '<p>Can you confirm the rollback window?</p>'
})
};
fetch('https://api.flashcat.cloud/member/notify?app_key=', 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.flashcat.cloud/member/notify?app_key=",
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([
'person_ids' => [
5068740052131,
5068740052132
],
'subject' => 'Incident 20260914-1 needs your input',
'html' => '<p>Can you confirm the rollback window?</p>'
]),
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.flashcat.cloud/member/notify?app_key="
payload := strings.NewReader("{\n \"person_ids\": [\n 5068740052131,\n 5068740052132\n ],\n \"subject\": \"Incident 20260914-1 needs your input\",\n \"html\": \"<p>Can you confirm the rollback window?</p>\"\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.flashcat.cloud/member/notify?app_key=")
.header("Content-Type", "application/json")
.body("{\n \"person_ids\": [\n 5068740052131,\n 5068740052132\n ],\n \"subject\": \"Incident 20260914-1 needs your input\",\n \"html\": \"<p>Can you confirm the rollback window?</p>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.flashcat.cloud/member/notify?app_key=")
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 \"person_ids\": [\n 5068740052131,\n 5068740052132\n ],\n \"subject\": \"Incident 20260914-1 needs your input\",\n \"html\": \"<p>Can you confirm the rollback window?</p>\"\n}"
response = http.request(request)
puts response.read_body{
"request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4",
"data": {
"recipients": [
{
"person_id": 5068740052131,
"status": "accepted"
},
{
"person_id": 5068740052132,
"status": "skipped",
"reason": "email_disabled"
}
]
}
}{
"request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4",
"error": {
"code": "InvalidParameter",
"message": "The specified parameter is not valid."
}
}{
"request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4",
"error": {
"code": "Unauthorized",
"message": "You are unauthorized."
}
}{
"request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4",
"error": {
"code": "AccessDenied",
"message": "Access Denied."
}
}{
"request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4",
"error": {
"code": "RequestTooFrequently",
"message": "Request too frequently."
}
}{
"request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4",
"error": {
"code": "InternalError",
"message": "We encountered an internal error, and it has been reported. Please try again later."
}
}Notify members
Send an email to account members on behalf of the caller, with content the caller supplies. Only callable with a credential minted for an AI SRE session; any other credential is rejected with AccessDenied. Delivery is asynchronous — accepted means the email was queued, not that it was delivered. Call it with dry_run set to true before sending: html in the response is the email exactly as recipients will get it, so you can confirm the sanitizer kept everything the message depends on.
curl --request POST \
--url 'https://api.flashcat.cloud/member/notify?app_key=' \
--header 'Content-Type: application/json' \
--data '
{
"person_ids": [
5068740052131,
5068740052132
],
"subject": "Incident 20260914-1 needs your input",
"html": "<p>Can you confirm the rollback window?</p>"
}
'import requests
url = "https://api.flashcat.cloud/member/notify?app_key="
payload = {
"person_ids": [5068740052131, 5068740052132],
"subject": "Incident 20260914-1 needs your input",
"html": "<p>Can you confirm the rollback window?</p>"
}
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({
person_ids: [5068740052131, 5068740052132],
subject: 'Incident 20260914-1 needs your input',
html: '<p>Can you confirm the rollback window?</p>'
})
};
fetch('https://api.flashcat.cloud/member/notify?app_key=', 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.flashcat.cloud/member/notify?app_key=",
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([
'person_ids' => [
5068740052131,
5068740052132
],
'subject' => 'Incident 20260914-1 needs your input',
'html' => '<p>Can you confirm the rollback window?</p>'
]),
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.flashcat.cloud/member/notify?app_key="
payload := strings.NewReader("{\n \"person_ids\": [\n 5068740052131,\n 5068740052132\n ],\n \"subject\": \"Incident 20260914-1 needs your input\",\n \"html\": \"<p>Can you confirm the rollback window?</p>\"\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.flashcat.cloud/member/notify?app_key=")
.header("Content-Type", "application/json")
.body("{\n \"person_ids\": [\n 5068740052131,\n 5068740052132\n ],\n \"subject\": \"Incident 20260914-1 needs your input\",\n \"html\": \"<p>Can you confirm the rollback window?</p>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.flashcat.cloud/member/notify?app_key=")
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 \"person_ids\": [\n 5068740052131,\n 5068740052132\n ],\n \"subject\": \"Incident 20260914-1 needs your input\",\n \"html\": \"<p>Can you confirm the rollback window?</p>\"\n}"
response = http.request(request)
puts response.read_body{
"request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4",
"data": {
"recipients": [
{
"person_id": 5068740052131,
"status": "accepted"
},
{
"person_id": 5068740052132,
"status": "skipped",
"reason": "email_disabled"
}
]
}
}{
"request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4",
"error": {
"code": "InvalidParameter",
"message": "The specified parameter is not valid."
}
}{
"request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4",
"error": {
"code": "Unauthorized",
"message": "You are unauthorized."
}
}{
"request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4",
"error": {
"code": "AccessDenied",
"message": "Access Denied."
}
}{
"request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4",
"error": {
"code": "RequestTooFrequently",
"message": "Request too frequently."
}
}{
"request_id": "01HK8XQE3Z7JM2NTFQ5YJ8P9R4",
"error": {
"code": "InternalError",
"message": "We encountered an internal error, and it has been reported. Please try again later."
}
}Restrictions
| Aspect | Value |
|---|---|
| Rate limits | 200 requests/minute; 10 requests/second per account |
| Permissions | None — callable only with an AI SRE session credential; any other credential is rejected with AccessDenied |
Usage
- Recipients that are not active members of the caller’s account, or that have no email address on file, are skipped rather than failing the whole request.
- Whether email is included follows each recipient’s own notification preferences for this kind of message; a recipient with no preference set defaults to receiving it.
- Recipients receive exactly the sanitized
htmlas the email body, with nothing added around it. The sender name shows the caller’s name followed by “(via AI SRE)”. - Set
dry_runtotrueto run every check and get the exact email back inhtmlwithout sending: nothing is queued, and neither the hourly limit nor the per-turn duplicate check is consumed. - At most 20 emails are delivered to the same recipient through this endpoint per hour; further deliveries to that recipient in the same window are skipped with
rate_limited. - Retrying the same call within the same AI SRE session turn does not send a duplicate email to a recipient who already received one; the repeat is skipped with
duplicate.
Authorizations
App key issued from the Flashduty console under Account → APP Keys. Required on every public API call. Keep it secret — it grants the same access as the owning account.
Body
Notify members by email request
Email subject, used as written. Required, 1–200 characters. Line breaks are replaced with a space; leading/trailing whitespace is trimmed.
1 - 200Email body as an HTML fragment (no <html>/<head>/<body> wrapper needed); recipients receive it as the whole email body. Required, up to 102,400 bytes of raw UTF-8 input (larger messages are clipped by common email clients), and must be non-empty after sanitization. Sanitized server-side: <script>, <style>, <iframe>, <object>, <embed>, <form>, <input>, <button>, <svg>, <meta>, <link>, and <base> tags and all on* event handlers are removed; images are kept only when their src is https — images with any other or no src, including data:, are removed; links are restricted to http, https, and mailto. Inline style attributes are kept as written.
102400Recipient member IDs. Optional, up to 20, no duplicates. Omitted or empty sends to the caller only.
20Check without sending. When true, every check runs and the response returns the exact email in html, but nothing is queued and neither the hourly limit nor the per-turn duplicate check is consumed. Defaults to false.
Response
Success
Success response envelope. On every 2xx response, request_id identifies the call (also mirrored in the Flashcat-Request-Id header) and data holds the endpoint-specific payload. Failure responses use a different shape — see ErrorResponse.
Was this page helpful?