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": "email html contains 2 construct(s) the sanitizer removes, which would change what recipients see. Fix and send again: <style> x2 - move these rules into inline style=\"...\" attributes; <img> without an https src x3 - host the image over https, or drop it"
}
}{
"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
Email account members a message written by the calling AI SRE session.
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": "email html contains 2 construct(s) the sanitizer removes, which would change what recipients see. Fix and send again: <style> x2 - move these rules into inline style=\"...\" attributes; <img> without an https src x3 - host the image over https, or drop it"
}
}{
"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 — but see the credential requirement under Usage |
Usage
- Only callable with a credential minted for an AI SRE session; any other credential is rejected with
AccessDenied. - Delivery is asynchronous —
acceptedmeans the email was queued, not that it was delivered. - 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)”. - Before sending, the server inspects the submitted
htmland rejects the request with400/InvalidParameterwhen it contains constructs whose removal would change what recipients see (<style>,<svg>,<iframe>,<object>,<embed>,<form>,<input>,<button>, an<img>with a missing or non-httpssrc, or an<a>whose href scheme is nothttp,https, ormailto); the error message names each construct and what to change, and nothing is queued — neither the hourly limit nor the per-turn duplicate check is consumed. Tags whose removal does not change rendering (<script>,<meta>,<link>,<base>,on*handlers) are still sanitized silently and never trigger a rejection. - 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 created in the Flashduty console under My → APP Key. Send it as the app_key query parameter or in the Fd-App-Key request header. Its effective access is the intersection of the key's permission scope and the creator's current role permissions. Keep it secret.
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. <script>, <meta>, <link>, and <base> tags and all on* event handlers are silently removed before sending, since email clients never execute them; inline style attributes are kept as written. <style>, <svg>, <iframe>, <object>, <embed>, <form>, <input>, and <button> tags, an <img> with a missing or non-https src (including data:), or an <a> whose href scheme is not http, https, or mailto are rejected instead of stripped: the request fails with 400 rather than silently changing what recipients see.
102400Recipient member IDs. Optional, up to 20, no duplicates. Omitted or empty sends to the caller only.
20Response
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?