Integrations
Ce contenu n’est pas encore disponible dans votre langue.
Apprise API is meant to be a single notification gateway. Instead of building direct webhooks to Discord, Slack, email, and SMS in every project, send to Apprise API and let it route the message.
Endpoints
Section titled “Endpoints”Stateful
Section titled “Stateful”For stateful notifications (recommended for most integrations), you call one endpoint:
POST /notify/{KEY}
Where {KEY} identifies a configuration saved on the server.
Your client code stays small and stable, while the Apprise API instance holds the actual
notification URLs and any routing tags.
Stateless
Section titled “Stateless”If you cannot (or do not want to) store configuration server-side, you can also use stateless notifications:
POST /notify
Stateless calls include the destination Apprise URLs in the request payload.
See the endpoint reference for the full list of supported paths.
Build the Request URL
Section titled “Build the Request URL”Given:
scheme:httporhttpshost: the Apprise API hostname or IPport: optional, omit for 80 (http) or 443 (https)key: your saved configuration key
Construct:
BASE = {scheme}://{host}- If a non-default port is used:
BASE = {scheme}://{host}:{port} NOTIFY = {BASE}/notify/{key}
Common Payload Fields
Section titled “Common Payload Fields”Apprise API accepts both form and JSON payloads in many cases. For integrations, JSON is usually easiest.
body(required): message contenttitle(optional): message titletype(optional):info(default),success,warning, orfailureformat(optional):text(default),markdown, orhtmltag(optional, stateful): route to a subset of saved URLs
Tags are only meaningful for stateful calls (/notify/{KEY}), because the server already
knows which URLs belong to that key.
Tag matching logic:
- Space,
+, or&means AND (intersection) - Comma or
|means OR (union)
Examples:
"devops,admin"notifies URLs taggeddevopsORadmin"devops critical"notifies URLs taggeddevopsANDcritical
Attachments
Section titled “Attachments”To send attachments, use multipart/form-data.
- Use
attach(recommended) orattachmentas the field name - The attachment value can be:
- a local file upload
- a remote URL that Apprise API downloads and forwards
Payload Mapping Hooks
Section titled “Payload Mapping Hooks”If a third-party tool cannot change its JSON keys, Apprise API can map incoming fields to Apprise fields using query parameters.
Syntax:
?:incoming_field=apprise_field
Example:
- A tool sends
{"message": "Server Down"} - Map it to Apprise
bodywith?:message=body
See the API usage documentation for more details.
Examples
Section titled “Examples”import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.net.http.HttpRequest.BodyPublishers;
public class AppriseNotifyStateless { public static void main(String[] args) { String url = "http://localhost:8000/notify";
// JSON payload with multiple URLs String jsonPayload = """ { "urls": [ "discord://TOKEN/CHANNEL", "mailto://user:pass@example.com" ], "body": "Hello from Apprise (stateless)" } """;
HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("Content-Type", "application/json") .POST(BodyPublishers.ofString(jsonPayload)) .build();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::body) .thenAccept(System.out::println) .join(); }}Here is an example using attachments:
// Note: We construct a manual multipart body here for standard Java 11+import java.io.IOException;import java.nio.file.Files;import java.nio.file.Path;import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.util.UUID;
public class AppriseAttachment { public static void main(String[] args) throws IOException, InterruptedException { String url = "http://localhost:8000/notify"; String boundary = "---Boundary" + UUID.randomUUID().toString(); Path file = Path.of("/path/to/file.txt");
// Comma-separated list of URLs String targetUrls = "discord://TOKEN/CHANNEL,mailto://user:pass@example.com";
// Build Multipart Body String body = "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"urls\"\r\n\r\n" + targetUrls + "\r\n" + "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"body\"\r\n\r\n" + "Artifact attached (Stateless)\r\n" + "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"attach\"; filename=\"" + file.getFileName() + "\"\r\n" + "Content-Type: application/octet-stream\r\n\r\n" + Files.readString(file) + "\r\n" + "--" + boundary + "--";
HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("Content-Type", "multipart/form-data; boundary=" + boundary) .POST(HttpRequest.BodyPublishers.ofString(body)) .build();
client.send(request, HttpResponse.BodyHandlers.discarding()); }}using System.Text;using System.Text.Json;
var url = "http://localhost:8000/notify";
var payload = new{ urls = new[] { "discord://TOKEN/CHANNEL", "mailto://user:pass@example.com" }, body = "Hello from Apprise (stateless)"};
using var client = new HttpClient();var json = JsonSerializer.Serialize(payload);var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content);response.EnsureSuccessStatusCode();Here is an example leveraging attachments:
using System.Net.Http.Headers;
var url = "http://localhost:8000/notify";var filePath = "/path/to/file.txt";
using var client = new HttpClient();using var form = new MultipartFormDataContent();
// 1. Add URLs (Comma separated string)form.Add(new StringContent("discord://TOKEN/CHANNEL,mailto://user:pass@example.com"), "urls");
// 2. Add Bodyform.Add(new StringContent("Artifact attached (Stateless)"), "body");
// 3. Add Attachmentvar fileBytes = await File.ReadAllBytesAsync(filePath);var fileContent = new ByteArrayContent(fileBytes);fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream");form.Add(fileContent, "attach", Path.GetFileName(filePath));
var response = await client.PostAsync(url, form);response.EnsureSuccessStatusCode();$Uri = "http://localhost:8000/notify"$Payload = @{ urls = @( "discord://TOKEN/CHANNEL", "mailto://user:pass@example.com" ) body = "Hello from Apprise (stateless)"}
Invoke-RestMethod -Uri $Uri ` -Method Post ` -Body ($Payload | ConvertTo-Json) ` -ContentType "application/json"Here is an example leveraging attachments:
$Uri = "http://localhost:8000/notify"$FilePath = "/path/to/file.txt"
# PowerShell 6.1+ automatically handles multipart if you use a Hash Table$Form = @{ # Pass URLs as a comma-separated string urls = "discord://TOKEN/CHANNEL,mailto://user:pass@example.com" body = "Artifact attached (Stateless)" attach = Get-Item -Path $FilePath}
Invoke-RestMethod -Uri $Uri -Method Post -Form $Formrequire 'net/http'require 'json'require 'uri'
uri = URI('http://localhost:8000/notify')http = Net::HTTP.new(uri.host, uri.port)request = Net::HTTP::Post.new(uri.path, {'Content-Type' => 'application/json'})
request.body = { urls: [ 'discord://TOKEN/CHANNEL', 'mailto://user:pass@example.com' ], body: 'Hello from Apprise (stateless)'}.to_json
response = http.request(request)puts response.bodyHere is an example leveraging attachments:
require 'net/http/post/multipart' # gem install multipart-postrequire 'uri'
url = URI.parse('http://localhost:8000/notify')file_path = '/path/to/file.txt'
File.open(file_path) do |file| req = Net::HTTP::Post::Multipart.new(url.path, # Pass URLs as a comma-separated string "urls" => "discord://TOKEN/CHANNEL,mailto://user:pass@example.com", "body" => "Artifact attached (Stateless)", "attach" => UploadIO.new(file, "application/octet-stream", File.basename(file_path)) )
Net::HTTP.start(url.host, url.port) do |http| http.request(req) endendThe following leverages the requests library:
from __future__ import annotations
from dataclasses import dataclassfrom urllib.parse import quote
import requests
@dataclass(frozen=True)class AppriseStatelessApi: scheme: str host: str port: int | None = None base_path: str = ""
@property def notify_url(self) -> str: origin = f"{self.scheme}://{self.host}:{self.port}" if self.port else f"{self.scheme}://{self.host}" prefix = self.base_path.strip("/") path = f"/{prefix}/notify/" if prefix else f"/notify/" return f"{origin}{path}"
api = AppriseStatelessApi(scheme="http", host="localhost", port=8000)payload = { "urls": ["discord://TOKEN/CHANNEL", "mailto://user:pass@example.com"], "body": "Hello from Apprise (stateless)",}
resp = requests.post( api.notify_url, json=payload, headers={"Accept": "application/json"}, timeout=30,)resp.raise_for_status()
# An attachment example:
files = { "attach": open("/path/to/file.txt", "rb")}payload = { "urls": "discord://TOKEN/CHANNEL,mailto://user:pass@example.com", "body": "Artifact attached"}
resp = requests.post( api.url, json=payload, headers={"Accept": "application/json"}, files=files, timeout=30,)resp.raise_for_status()The following leverages the aiohttp library:
from __future__ import annotations
from dataclasses import dataclassfrom pathlib import Pathfrom typing import Anyfrom urllib.parse import quote
import asyncioimport aiohttp
@dataclass(frozen=True)class AppriseStatelessApi: scheme: str host: str port: int | None = None base_path: str = ""
@property def notify_url(self) -> str: origin = f"{self.scheme}://{self.host}:{self.port}" if self.port else f"{self.scheme}://{self.host}" prefix = self.base_path.strip("/") path = f"/{prefix}/notify/" if prefix else f"/notify/" return f"{origin}{path}"
async def apprise_notify_json(url: str, payload: dict[str, Any]) -> None: timeout = aiohttp.ClientTimeout(total=30) async with aiohttp.ClientSession(timeout=timeout, headers={"Accept": "application/json"}) as session: async with session.post(url, json=payload) as resp: resp.raise_for_status()
async def apprise_notify_with_attachment(url: str, body: str, file_path: str) -> None: path = Path(file_path) form = aiohttp.FormData() form.add_field("body", body) # Field name can be attach or attachment form.add_field("attach", path.read_bytes(), filename=path.name)
timeout = aiohttp.ClientTimeout(total=60) async with aiohttp.ClientSession(timeout=timeout, headers={"Accept": "application/json"}) as session: async with session.post(url, data=form) as resp: resp.raise_for_status()
# Usage:api = AppriseStatelessApi(scheme="http", host="localhost", port=8000)
# Send our notification:asyncio.run(apprise_notify_json(api.notify_url, {"body": "Hello"}))
# Send our notification with an attachment:asyncio.run(apprise_notify_with_attachment( api.notify_url, { "title": "Strange abuse detected", "body": "See logs for details", "type": "warning", "tag": "cyber-team", }, '/var/log/nginx/access.log'))# Minimal stateless notification using curlcurl -X POST "http://localhost:8000/notify" \ -H "Content-Type: application/json" \ -d '{ "urls": [ "discord://TOKEN/CHANNEL", "mailto://user:pass@example.com" ], "body": "Hello from Apprise (stateless)" }'# Stateless notification with an attachmentcurl -X POST "http://localhost:8000/notify" \ -F 'payload={ "urls": ["discord://TOKEN/CHANNEL"], "title": "Build Complete", "body": "Artifact attached" };type=application/json' \ -F "attach=@/path/to/file.txt"// Stateless Apprise API call using fetch (TypeScript)const scheme = "http";const host = "localhost";const port = 8000;
const url = `${scheme}://${host}:${port}/notify`;
const payload = { urls: ["discord://TOKEN/CHANNEL", "slack://TOKEN/CHANNEL"], body: "Hello from Apprise (stateless)",};
await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload),});// Stateless Apprise API call using fetch (Vanilla JS)const scheme = "http";const host = "localhost";const port = 8000;
fetch(`${scheme}://${host}:${port}/notify`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ urls: ["discord://TOKEN/CHANNEL"], body: "Hello from Apprise (stateless)", }),});package main
import ( "bytes" "encoding/json" "net/http")
func main() { payload := map[string]any{ "urls": []string{ "discord://TOKEN/CHANNEL", "mailto://user:pass@example.com", }, "body": "Hello from Apprise (stateless)", }
data, _ := json.Marshal(payload)
http.Post( "http://localhost:8000/notify", "application/json", bytes.NewBuffer(data), )}use reqwest::blocking::Client;use serde_json::json;
fn main() -> Result<(), Box<dyn std::error::Error>> { let client = Client::new();
let payload = json!({ "urls": [ "discord://TOKEN/CHANNEL", "slack://TOKEN/CHANNEL" ], "body": "Hello from Apprise (stateless)" });
client .post("http://localhost:8000/notify") .json(&payload) .send()?;
Ok(())}<?php// Stateless Apprise API call using PHP cURL
$payload = json_encode([ "urls" => [ "discord://TOKEN/CHANNEL", "mailto://user:pass@example.com" ], "body" => "Hello from Apprise (stateless)"]);
$ch = curl_init("http://localhost:8000/notify");curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Content-Type: application/json"], CURLOPT_POSTFIELDS => $payload,]);
curl_exec($ch);curl_close($ch);# JSON payload (recommended)SCHEME=httpHOST=localhostPORT=8000KEY=my-alerts
BASE="$SCHEME://$HOST"if [ ! -z "$PORT" ]; then BASE="$SCHEME://$HOST:$PORT"fiURL="$BASE/notify/$KEY"
curl -X POST \ -F "title=Build" \ -F "type=success" \ -F "body=Build complete" \ "$URL"Here is another example tha routes based on defined tags:
curl -X POST \ -F "type=warning" \ -F "tag=devops critical" \ -F "body=Disk space low" \ "$URL"# Attachment (multipart)curl -X POST \ -F "body=See attached log" \ -F "attach=@/var/log/syslog" \ "$URL"type AppriseType = "info" | "success" | "warning" | "failure";type AppriseFormat = "text" | "markdown" | "html";
interface NotifyPayload { body: string; title?: string; type?: AppriseType; format?: AppriseFormat; tag?: string;}
function buildNotifyUrl( scheme: "http" | "https", host: string, key: string, port?: number, basePath: string = "",): string { const origin = port ? `${scheme}://${host}:${port}` : `${scheme}://${host}`; const prefix = basePath ? `/${basePath.replace(/^\/+|\/+$/g, "")}` : ""; return `${origin}${prefix}/notify/${encodeURIComponent(key)}`;}
export async function appriseNotifyJson( url: string, payload: NotifyPayload,): Promise<void> { const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify(payload), });
if (!res.ok) { const text = await res.text().catch(() => ""); throw new Error(`Apprise API failed: ${res.status} ${text}`); }}
export async function appriseNotifyWithAttachment( url: string, payload: NotifyPayload, file: File,): Promise<void> { const form = new FormData(); if (payload.title) form.append("title", payload.title); if (payload.type) form.append("type", payload.type); if (payload.format) form.append("format", payload.format); if (payload.tag) form.append("tag", payload.tag); form.append("body", payload.body);
// Field name can be attach or attachment form.append("attach", file);
const res = await fetch(url, { method: "POST", headers: { Accept: "application/json", }, body: form, });
if (!res.ok) { const text = await res.text().catch(() => ""); throw new Error(`Apprise API failed: ${res.status} ${text}`); }}
// Example:const notifyUrl = buildNotifyUrl( "https", "apprise.example.net", "my-alerts", 443,);await appriseNotifyJson(notifyUrl, { title: "Deploy", body: "Deployed to production", type: "success", tag: "devops,admin",});function buildNotifyUrl({ scheme, host, port, key, basePath = "" }) { const origin = port ? `${scheme}://${host}:${port}` : `${scheme}://${host}`; const prefix = basePath ? `/${basePath.replace(/^\/+|\/+$/g, "")}` : ""; return `${origin}${prefix}/notify/${encodeURIComponent(key)}`;}
async function appriseNotify(url, payload) { const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify(payload), });
if (!res.ok) { const text = await res.text().catch(() => ""); throw new Error(`Apprise API failed: ${res.status} ${text}`); }}
// Example:const url = buildNotifyUrl({ scheme: "http", host: "localhost", port: 8000, key: "my-alerts",});
await appriseNotify(url, { title: "Backup", body: "Backup completed successfully.", type: "success",});<?php
function build_notify_url(string $scheme, string $host, string $key, ?int $port = null, string $base_path = ''): string { $origin = $port ? sprintf('%s://%s:%d', $scheme, $host, $port) : sprintf('%s://%s', $scheme, $host); $prefix = trim($base_path, '/'); $path = $prefix ? sprintf('/%s/notify/%s', $prefix, rawurlencode($key)) : sprintf('/notify/%s', rawurlencode($key)); return $origin . $path;}
function apprise_notify_json(string $url, array $payload): void { $ch = curl_init($url); if ($ch === false) { throw new RuntimeException('Failed to init curl'); }
$json = json_encode($payload); if ($json === false) { throw new RuntimeException('Failed to encode JSON'); }
curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Accept: application/json', ], CURLOPT_POSTFIELDS => $json, ]);
$resp = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($resp === false) { $err = curl_error($ch); curl_close($ch); throw new RuntimeException('Apprise API request failed: ' . $err); }
curl_close($ch);
if ($code < 200 || $code >= 300) { throw new RuntimeException(sprintf('Apprise API failed: %d %s', $code, trim((string)$resp))); }}
function apprise_notify_with_attachment(string $url, string $body, string $file_path): void { $ch = curl_init($url); if ($ch === false) { throw new RuntimeException('Failed to init curl'); }
$post = [ 'body' => $body, // Field name can be attach or attachment 'attach' => new CURLFile($file_path), ];
curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Accept: application/json', ], CURLOPT_POSTFIELDS => $post, ]);
$resp = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($resp === false) { $err = curl_error($ch); curl_close($ch); throw new RuntimeException('Apprise API request failed: ' . $err); }
curl_close($ch);
if ($code < 200 || $code >= 300) { throw new RuntimeException(sprintf('Apprise API failed: %d %s', $code, trim((string)$resp))); }}
// Example:$url = build_notify_url('http', 'localhost', 'my-alerts', 8000);
apprise_notify_json($url, [ 'title' => 'Deploy', 'body' => 'Deployed to production', 'type' => 'success', 'tag' => 'devops,admin',]);
apprise_notify_with_attachment($url, 'See attached log', '/var/log/syslog');using System.Text;using System.Text.Json;
var url = "<http://localhost:8000/notify/my-alerts>";var payload = new{title = "Deploy",body = "Deployed to production",type = "success",tag = "devops,admin"};
using var client = new HttpClient();var json = JsonSerializer.Serialize(payload);var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content);response.EnsureSuccessStatusCode();Below is an attachment example:
using var client = new HttpClient();using var form = new MultipartFormDataContent();
form.Add(new StringContent("See attached log"), "body");// Open the file streamusing var fileStream = File.OpenRead("/var/log/syslog");using var fileContent = new StreamContent(fileStream);form.Add(fileContent, "attach", "syslog.txt");
var response = await client.PostAsync("http://localhost:8000/notify/my-alerts", form);response.EnsureSuccessStatusCode();$Uri = "http://localhost:8000/notify/my-alerts"$Payload = @{ title = "Deploy" body = "Deployed to production" type = "success" tag = "devops,admin"}
Invoke-RestMethod -Uri $Uri -Method Post -Body ($Payload | ConvertTo-Json) -ContentType "application/json"Below is an attachment example:
$Uri = "http://localhost:8000/notify/my-alerts"$Form = @{ body = "See attached log" attach = Get-Item -Path "/var/log/syslog"}
Invoke-RestMethod -Uri $Uri -Method Post -Form $Formrequire 'net/http'require 'json'require 'uri'
uri = URI('<http://localhost:8000/notify/my-alerts>')http = Net::HTTP.new(uri.host, uri.port)request = Net::HTTP::Post.new(uri.path, {'Content-Type' => 'application/json'})
request.body = {title: 'Deploy',body: 'Deployed to production',type: 'success',tag: 'devops,admin'}.to_json
response = http.request(request)puts response.bodyBelow is an attachment example:
require 'net/http/post/multipart' # gem install multipart-post
url = URI.parse('http://localhost:8000/notify/my-alerts')
File.open('/var/log/syslog') do |file| req = Net::HTTP::Post::Multipart.new(url.path, "body" => "See attached log", "attach" => UploadIO.new(file, "text/plain", "syslog.txt") )
res = Net::HTTP.start(url.host, url.port) do |http| http.request(req) endendThe following leverages the requests library:
from __future__ import annotations
from dataclasses import dataclassfrom urllib.parse import quote
import requests
@dataclass(frozen=True)class AppriseApi: scheme: str host: str key: str port: int | None = None base_path: str = ""
@property def notify_url(self) -> str: origin = f"{self.scheme}://{self.host}:{self.port}" if self.port else f"{self.scheme}://{self.host}" prefix = self.base_path.strip("/") path = f"/{prefix}/notify/{quote(self.key)}" if prefix else f"/notify/{quote(self.key)}" return f"{origin}{path}"
# Usage:api = AppriseApi(scheme="http", host="localhost", port=8000, key="my-alerts")payload = { "title": "Deploy", "body": "Deployed to production", "type": "success", "tag": "devops,admin",}
resp = requests.post( api.url, json=payload, headers={"Accept": "application/json"}, timeout=30,)resp.raise_for_status()The following leverages the aiohttp library:
from __future__ import annotations
from dataclasses import dataclassfrom pathlib import Pathfrom typing import Anyfrom urllib.parse import quote
import asyncioimport aiohttp
@dataclass(frozen=True)class AppriseApi: scheme: str host: str key: str port: int | None = None base_path: str = ""
@property def notify_url(self) -> str: origin = f"{self.scheme}://{self.host}:{self.port}" if self.port else f"{self.scheme}://{self.host}" prefix = self.base_path.strip("/") path = f"/{prefix}/notify/{quote(self.key)}" if prefix else f"/notify/{quote(self.key)}" return f"{origin}{path}"
async def apprise_notify_json(url: str, payload: dict[str, Any]) -> None: timeout = aiohttp.ClientTimeout(total=30) async with aiohttp.ClientSession(timeout=timeout, headers={"Accept": "application/json"}) as session: async with session.post(url, json=payload) as resp: resp.raise_for_status()
async def apprise_notify_with_attachment(url: str, body: str, file_path: str) -> None: path = Path(file_path) form = aiohttp.FormData() form.add_field("body", body) # Field name can be attach or attachment form.add_field("attach", path.read_bytes(), filename=path.name)
timeout = aiohttp.ClientTimeout(total=60) async with aiohttp.ClientSession(timeout=timeout, headers={"Accept": "application/json"}) as session: async with session.post(url, data=form) as resp: resp.raise_for_status()
# Usage:api = AppriseApi(scheme="http", host="localhost", port=8000, key="my-alerts")
# Send our notification:asyncio.run(apprise_notify_json(api.notify_url, {"body": "Hello"}))
# Send our notification with an attachment:asyncio.run(apprise_notify_with_attachment( api.notify_url, { "title": "Strange abuse detected", "body": "See logs for details", "type": "warning", "tag": "cyber-team", }, '/var/log/nginx/access.log'))import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;import java.net.http.HttpRequest.BodyPublishers;
public class AppriseNotify { public static void main(String[] args) { String url = "http://localhost:8000/notify/my-alerts"; String jsonPayload = """ { "title": "Deploy", "body": "Deployed to production", "type": "success", "tag": "devops,admin" } """;
HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("Content-Type", "application/json") .header("Accept", "application/json") .POST(BodyPublishers.ofString(jsonPayload)) .build();
client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::body) .thenAccept(System.out::println) .join(); }}package main
import ( "bytes" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "net/url" "os" "path" "strings")
type NotifyPayload struct { Body string `json:"body"` Title string `json:"title,omitempty"` Type string `json:"type,omitempty"` Format string `json:"format,omitempty"` Tag string `json:"tag,omitempty"`}
func buildNotifyURL(scheme, host, key string, port int, basePath string) (string, error) { u := &url.URL{Scheme: scheme, Host: host} if port != 0 { u.Host = fmt.Sprintf("%s:%d", host, port) } cleanBase := strings.Trim(basePath, "/") if cleanBase != "" { u.Path = "/" + cleanBase } u.Path = path.Join(u.Path, "notify", key) return u.String(), nil}
func notifyJSON(endpoint string, payload NotifyPayload) error { b, err := json.Marshal(payload) if err != nil { return err }
req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(b)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json")
res, err := http.DefaultClient.Do(req) if err != nil { return err } defer res.Body.Close()
if res.StatusCode < 200 || res.StatusCode >= 300 { body, _ := io.ReadAll(res.Body) return fmt.Errorf("apprise api failed: %d %s", res.StatusCode, strings.TrimSpace(string(body))) }
return nil}
func notifyWithAttachment(endpoint string, payload NotifyPayload, filePath string) error { var buf bytes.Buffer w := multipart.NewWriter(&buf)
// Fields _ = w.WriteField("body", payload.Body) if payload.Title != "" { _ = w.WriteField("title", payload.Title) } if payload.Type != "" { _ = w.WriteField("type", payload.Type) } if payload.Format != "" { _ = w.WriteField("format", payload.Format) } if payload.Tag != "" { _ = w.WriteField("tag", payload.Tag) }
// File f, err := os.Open(filePath) if err != nil { return err } defer f.Close()
part, err := w.CreateFormFile("attach", path.Base(filePath)) if err != nil { return err } if _, err = io.Copy(part, f); err != nil { return err }
if err = w.Close(); err != nil { return err }
req, err := http.NewRequest(http.MethodPost, endpoint, &buf) if err != nil { return err } req.Header.Set("Content-Type", w.FormDataContentType()) req.Header.Set("Accept", "application/json")
res, err := http.DefaultClient.Do(req) if err != nil { return err } defer res.Body.Close()
if res.StatusCode < 200 || res.StatusCode >= 300 { body, _ := io.ReadAll(res.Body) return fmt.Errorf("apprise api failed: %d %s", res.StatusCode, strings.TrimSpace(string(body))) }
return nil}
func main() { endpoint, err := buildNotifyURL("http", "localhost", "my-alerts", 8000, "") if err != nil { panic(err) }
_ = notifyJSON(endpoint, NotifyPayload{ Title: "Deploy", Body: "Deployed to production", Type: "success", Tag: "devops,admin", })
_ = notifyWithAttachment(endpoint, NotifyPayload{Body: "See attached log"}, "/var/log/syslog")}use reqwest::multipart;use reqwest::Client;use serde::Serialize;
#[derive(Serialize)]struct NotifyPayload<'a> { body: &'a str, #[serde(skip_serializing_if = "Option::is_none")] title: Option<&'a str>, #[serde(skip_serializing_if = "Option::is_none")] r#type: Option<&'a str>, #[serde(skip_serializing_if = "Option::is_none")] format: Option<&'a str>, #[serde(skip_serializing_if = "Option::is_none")] tag: Option<&'a str>,}
fn build_notify_url(scheme: &str, host: &str, key: &str, port: Option<u16>, base_path: Option<&str>) -> String { let origin = match port { Some(p) => format!("{}://{}:{}", scheme, host, p), None => format!("{}://{}", scheme, host), };
let prefix = base_path .unwrap_or("") .trim_matches('/') .to_string();
if prefix.is_empty() { format!("{}/notify/{}", origin, urlencoding::encode(key)) } else { format!("{}/{}/notify/{}", origin, prefix, urlencoding::encode(key)) }}
async fn notify_json(endpoint: &str, payload: NotifyPayload<'_>) -> Result<(), reqwest::Error> { let res = Client::new() .post(endpoint) .header("Accept", "application/json") .json(&payload) .send() .await?;
if !res.status().is_success() { let text = res.text().await.unwrap_or_default(); panic!("Apprise API failed: {} {}", res.status(), text); }
Ok(())}
async fn notify_with_attachment(endpoint: &str, body: &str, file_path: &str) -> Result<(), reqwest::Error> { let form = multipart::Form::new() .text("body", body.to_string()) .file("attach", file_path)?;
let res = Client::new() .post(endpoint) .header("Accept", "application/json") .multipart(form) .send() .await?;
if !res.status().is_success() { let text = res.text().await.unwrap_or_default(); panic!("Apprise API failed: {} {}", res.status(), text); }
Ok(())}
#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let endpoint = build_notify_url("http", "localhost", "my-alerts", Some(8000), None);
notify_json( &endpoint, NotifyPayload { title: Some("Deploy"), body: "Deployed to production", r#type: Some("success"), format: None, tag: Some("devops,admin"), }, ) .await?;
notify_with_attachment(&endpoint, "See attached log", "/var/log/syslog").await?;
Ok(())}