curl --request POST \
--url https://notifications.{region}.basaltic.sh/v1/topics/{topic_id}/publish \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"message": "osd.22 reports a firmware impending failure",
"subject": "Disk pressure on europa",
"message_structure": "json",
"attributes": {
"severity": {
"type": "String",
"value": "critical"
},
"latency": {
"type": "Number",
"value": "250"
}
}
}
'import requests
url = "https://notifications.{region}.basaltic.sh/v1/topics/{topic_id}/publish"
payload = {
"message": "osd.22 reports a firmware impending failure",
"subject": "Disk pressure on europa",
"message_structure": "json",
"attributes": {
"severity": {
"type": "String",
"value": "critical"
},
"latency": {
"type": "Number",
"value": "250"
}
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
message: 'osd.22 reports a firmware impending failure',
subject: 'Disk pressure on europa',
message_structure: 'json',
attributes: {
severity: {type: 'String', value: 'critical'},
latency: {type: 'Number', value: '250'}
}
})
};
fetch('https://notifications.{region}.basaltic.sh/v1/topics/{topic_id}/publish', 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://notifications.{region}.basaltic.sh/v1/topics/{topic_id}/publish",
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' => 'osd.22 reports a firmware impending failure',
'subject' => 'Disk pressure on europa',
'message_structure' => 'json',
'attributes' => [
'severity' => [
'type' => 'String',
'value' => 'critical'
],
'latency' => [
'type' => 'Number',
'value' => '250'
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"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://notifications.{region}.basaltic.sh/v1/topics/{topic_id}/publish"
payload := strings.NewReader("{\n \"message\": \"osd.22 reports a firmware impending failure\",\n \"subject\": \"Disk pressure on europa\",\n \"message_structure\": \"json\",\n \"attributes\": {\n \"severity\": {\n \"type\": \"String\",\n \"value\": \"critical\"\n },\n \"latency\": {\n \"type\": \"Number\",\n \"value\": \"250\"\n }\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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://notifications.{region}.basaltic.sh/v1/topics/{topic_id}/publish")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"message\": \"osd.22 reports a firmware impending failure\",\n \"subject\": \"Disk pressure on europa\",\n \"message_structure\": \"json\",\n \"attributes\": {\n \"severity\": {\n \"type\": \"String\",\n \"value\": \"critical\"\n },\n \"latency\": {\n \"type\": \"Number\",\n \"value\": \"250\"\n }\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://notifications.{region}.basaltic.sh/v1/topics/{topic_id}/publish")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"message\": \"osd.22 reports a firmware impending failure\",\n \"subject\": \"Disk pressure on europa\",\n \"message_structure\": \"json\",\n \"attributes\": {\n \"severity\": {\n \"type\": \"String\",\n \"value\": \"critical\"\n },\n \"latency\": {\n \"type\": \"Number\",\n \"value\": \"250\"\n }\n }\n}"
response = http.request(request)
puts response.read_body{
"message_id": "019400aa-bbbb-7ccc-8ddd-eeeeffff0000"
}{
"error": {
"code": "INVALID_INPUT",
"message": "Invalid request parameters",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "UNAUTHORIZED",
"message": "Authentication required",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "ACCESS_DENIED",
"message": "You don't have permission to perform this action",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "NOT_FOUND",
"message": "Resource not found",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "CONFLICT",
"message": "Resource with this name already exists",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "IDEMPOTENCY_KEY_REUSED",
"message": "This Idempotency-Key was already used with a different request payload",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}Publish a notification
Returns 202 once the notification is durable and queued. Fan-out
into per-subscription deliveries has NOT run at that point —
delivery_count on the message is 0 until it does.
curl --request POST \
--url https://notifications.{region}.basaltic.sh/v1/topics/{topic_id}/publish \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"message": "osd.22 reports a firmware impending failure",
"subject": "Disk pressure on europa",
"message_structure": "json",
"attributes": {
"severity": {
"type": "String",
"value": "critical"
},
"latency": {
"type": "Number",
"value": "250"
}
}
}
'import requests
url = "https://notifications.{region}.basaltic.sh/v1/topics/{topic_id}/publish"
payload = {
"message": "osd.22 reports a firmware impending failure",
"subject": "Disk pressure on europa",
"message_structure": "json",
"attributes": {
"severity": {
"type": "String",
"value": "critical"
},
"latency": {
"type": "Number",
"value": "250"
}
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
message: 'osd.22 reports a firmware impending failure',
subject: 'Disk pressure on europa',
message_structure: 'json',
attributes: {
severity: {type: 'String', value: 'critical'},
latency: {type: 'Number', value: '250'}
}
})
};
fetch('https://notifications.{region}.basaltic.sh/v1/topics/{topic_id}/publish', 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://notifications.{region}.basaltic.sh/v1/topics/{topic_id}/publish",
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' => 'osd.22 reports a firmware impending failure',
'subject' => 'Disk pressure on europa',
'message_structure' => 'json',
'attributes' => [
'severity' => [
'type' => 'String',
'value' => 'critical'
],
'latency' => [
'type' => 'Number',
'value' => '250'
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"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://notifications.{region}.basaltic.sh/v1/topics/{topic_id}/publish"
payload := strings.NewReader("{\n \"message\": \"osd.22 reports a firmware impending failure\",\n \"subject\": \"Disk pressure on europa\",\n \"message_structure\": \"json\",\n \"attributes\": {\n \"severity\": {\n \"type\": \"String\",\n \"value\": \"critical\"\n },\n \"latency\": {\n \"type\": \"Number\",\n \"value\": \"250\"\n }\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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://notifications.{region}.basaltic.sh/v1/topics/{topic_id}/publish")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"message\": \"osd.22 reports a firmware impending failure\",\n \"subject\": \"Disk pressure on europa\",\n \"message_structure\": \"json\",\n \"attributes\": {\n \"severity\": {\n \"type\": \"String\",\n \"value\": \"critical\"\n },\n \"latency\": {\n \"type\": \"Number\",\n \"value\": \"250\"\n }\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://notifications.{region}.basaltic.sh/v1/topics/{topic_id}/publish")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"message\": \"osd.22 reports a firmware impending failure\",\n \"subject\": \"Disk pressure on europa\",\n \"message_structure\": \"json\",\n \"attributes\": {\n \"severity\": {\n \"type\": \"String\",\n \"value\": \"critical\"\n },\n \"latency\": {\n \"type\": \"Number\",\n \"value\": \"250\"\n }\n }\n}"
response = http.request(request)
puts response.read_body{
"message_id": "019400aa-bbbb-7ccc-8ddd-eeeeffff0000"
}{
"error": {
"code": "INVALID_INPUT",
"message": "Invalid request parameters",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "UNAUTHORIZED",
"message": "Authentication required",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "ACCESS_DENIED",
"message": "You don't have permission to perform this action",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "NOT_FOUND",
"message": "Resource not found",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "CONFLICT",
"message": "Resource with this name already exists",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "IDEMPOTENCY_KEY_REUSED",
"message": "This Idempotency-Key was already used with a different request payload",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}notifications:Publish. See NOTIFICATIONS permissions for the full list, what each one covers, and an example policy.Authorizations
An OAuth 2.0 bearer token, sent as Authorization: Bearer <token>.
This is the recommended way to authenticate.
Get one by exchanging a service account's access key pair at
POST /v1/oauth/token with grant_type=client_credentials. It is the
standard client-credentials grant, so any OAuth-aware library will
obtain and refresh it for you.
curl -s -u "$KEY_ID:$SECRET" -d grant_type=client_credentials \
https://iam.basaltic.sh/v1/oauth/token
Tokens last an hour by default. The same access key pair is separately your AWS SigV4 credential for the S3-compatible object endpoint, which speaks nothing else.
Headers
Optional client-generated key that makes a create replay-safe. Retrying a request with the same key returns the original outcome verbatim instead of creating a duplicate resource. Reusing a key with a different request body is rejected (422); a request whose key is still being processed returns 409. Records are honored for 24 hours. Use a UUID or similarly unique token.
255Path Parameters
"a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
Body
The payload. Up to 256 KiB.
262144"osd.22 reports a firmware impending failure"
Used as the email subject. No line breaks.
100"Disk pressure on europa"
Omit to deliver message verbatim to every protocol. Set to
json to have message read as an object of protocol keys
(default, email, https, http), letting one publish
carry a different payload per transport. A default key is
required — it is what a protocol with no explicit entry gets.
, json "json"
Typed attributes carried with a notification and matched against subscription filter policies. At most 20 attributes; values at most 1024 bytes. Binary attributes are not accepted — they cannot be filtered on.
Show child attributes
Show child attributes
{
"severity": { "type": "String", "value": "critical" },
"latency": { "type": "Number", "value": "250" }
}
Response
Notification accepted for delivery.
"019400aa-bbbb-7ccc-8ddd-eeeeffff0000"

