Introduction
Pulsetray is a notification service that allows you to send push notifications to your own devices or to devices belonging to others (family members, coworkers, or employees). Notifications are triggered via a simple API request, making it easy to integrate with Home Assistant, custom scripts, servers, or CI/CD pipelines.
Beyond simply receiving alerts, Pulsetray features a dedicated notification tray. This allows you to maintain a searchable history of all sent alerts, complete with filtering by custom sources and categories.
Pulsetray is designed to be flexible. It can function as a primary notifications provider or as a persistent, searchable archive for existing setups lacking a native notifications tray (Home Assistant, for example).
Key features
- API-First: Trigger notifications from any environment that supports HTTP requests.
- Rich Media: Support for images, GIFs, videos, and actionable URLs.
- Granular Targeting: Send alerts to your entire account, specific profiles, or individual devices.
- Structured Metadata: Organize notifications by source and category for advanced tracking.
- Persistent History: A searchable, centralized tray within the app to manage your notification log.
Core Concepts
To get the most out of Pulsetray, it’s helpful to understand how your data is organized. Currently, Pulsetray is available as an Android App, which serves as your hub for receiving alerts and managing your account settings. To send notifications to your devices, there's a Public API.
When you sign up, an Account is created with you as the owner. Your initial device is registered to this account. To add additional devices, you have three distinct methods:
- Via Onboarding Token: Create a Profile, add a device under that profile, and share the generated Onboarding Token. This provides "Receive-only" access (see Onboarding Devices).
- Via Onboarding URL: The fastest way to invite others. Send a unique link to the recipient; clicking it will automatically launch Pulsetray and pre-fill the onboarding token for them.
- Via Account Login: Designed for the Account Owner. Sign into your existing Pulsetray account on a new device to grant it Full Access. This allows the device to manage settings, API keys, and notification history.
For a detailed comparison of these access levels, please see Authentication.
Quick Start
To begin, download the Android App, create your account, and complete the initial setup.
Get your first notification with two simple steps:
- Generate an API Key: In the app, navigate to Settings > API Keys > Add API Key. Once created, copy the generated Token.
- Send the Notification: Use the cURL sample below to fire your first request. Replace
YOUR_TOKENwith the key you just generated.
curl -X POST https://api.pulsetray.com/v1/notifications \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{ "title": "Hello World", "body": "First notification sent!" }'Done! You'll receive a push notification to your device, and the notification will show up in the tray.
To see the endpoint's full reference, see Sending notifications
Authentication
There are two distinct ways to access the Pulsetray app, depending on whether you need to manage the account or simply receive notifications.
Access Levels at a Glance
| Feature | Account login (Full access) | Onboard device (Restricted access) |
|---|---|---|
| Receive notifications | ✅ | ✅ |
| Delete notifications | ✅ | ❌ |
| Manage sources/categories | ✅ | ❌ |
| Manage profiles/devices | ✅ | ❌ |
| Manage API keys | ✅ | ❌ |
| Manage account | ✅ | ❌ |
1. Account login (full access)
Use your email/password or Google Sign-in. This is intended for the owner of the Pulsetray account.
Registering your device
Upon signing in, you will be prompted to register your current device under your profile. You can either register it as a New Device or choose from your profile Unregistered Devices list.
Important Note: A device only appears in the "Unregistered" list if you explicitly tapped Sign Out within the Pulsetray app.
If you uninstall the app without signing out first, the device will remain active in our system, and it cannot be registered again. In this case, you must manually delete the old device from the Profiles and Devices screen to free up the slot.
2. Onboard device (restricted access)
On the welcome screen, users can select Onboard Device. This is ideal for inviting others to your account without giving them administrative control.
To onboard a device, you must first create a device entry in your own Pulsetray app (see Profiles & Devices). From there, you can share the Onboarding Token or, for a faster experience, the Onboarding URL.
To see more information about onboarding devices, see Onboarding devices.
Notifications
The Notifications screen is the heart of the app. It displays a real-time list of alerts your device is authorized to see.
Targeting & Visibility
A notification appears in your tray if it meets any of the following criteria:
- It was targeted to the Entire Account (default).
- It was targeted to your specific Profile.
- It was targeted specifically to your Device ID.
Retroactive Sync: Because Pulsetray stores history of notifications, notifications are not lost if a device is not yet registered. If you add a new device to a profile tomorrow, it will automatically populate with all past notifications previously sent to that profile or account.
Within the tray, you can:
- Search through your notification history.
- Filter by Source (including built-in system sources), Category, Read/Unread status, or Date.
- Mark items as read by tapping them.
- View or play media attachments (images, GIFs, videos).
- Delete notifications: Note that this action is only available to users signed in with Full Access. Deleting a notification removes it from the entire account's history.
Sources & Categories
Pulsetray allows you to organize your notifications using a two-tier classification system: Sources and Categories. These help you identify where an alert originated and why it was triggered.
To manage these, navigate to Settings > Sources and Categories in the app. Each Source acts as a top-level container and includes the following attributes:
- Code: A unique identifier used in your API requests (e.g.,
home-assistantorgithub-actions). - Name: The display label that appears in your notification history (e.g.,
Home Assistant). - Color: A custom color assigned to the source tag for quick visual identification in the tray.
Notification Channels
In your Android device, for each source you create a new Notification Channel will also be created. This gives you granular control of the behavior of all notifications based on the origin where they came from. To see the notification channels, go to the Pulsetray notification preferences in your device settings. (This is typically in Settings > Notifications > App notifications > Pulsetray > Notification categories)
Managing Categories
Categories are nested within Sources, allowing for even deeper organization. For example, a Server source might have categories for Backup or Security, or a Home Assistant source might have categories for Garage door, Camera 1, Living Room sensor, etc.
To manage categories for a specific source, select that source from the management screen. Categories share the same attribute structure:
- Code: The unique identifier for the category (e.g.,
prod-pipeline). - Name: The display label (e.g.,
Production Pipeline). - Color: The UI color for the category tag.
Organization Logic
Source: home-assistant (Home Assistant)
└── Category: front-door (Front Door)
When sending a notification via the API, these identifiers are optional. You can send a basic alert with no tags, attach just a Source, or provide both a Source and Category to narrow it down. Note: A Category must always be associated with a Source.
Note on System Sources: You may notice a built-in source named Pulsetray with categories such as Account, Announcement, or Security when filtering your notification history. These are system-level sources used for official alerts; because they are system-managed and cannot be modified, they appear in the filtering menus but are hidden from the Settings management screen. System notifications target your profile only.
Profiles & devices
Pulsetray allows you to manage multiple recipients by organizing them into Profiles and Devices. This structure gives you the flexibility to target specific people (and their devices), or specific devices.
To manage them, navigate to Settings > Profiles and devices.
Managing Profiles
The main management screen displays a list of all profiles and the number of devices assigned to each.
- The "YOU" Profile: This represents your own identity. It contains the device you are currently using to manage the account.
- Adding Profiles: Tap Add profile and provide a name (e.g., "Jane Doe").
- Profile Details: Tapping a profile allows you to rename it, delete it entirely, or manage its devices. Deleting a profile will sign out all devices associated with it, and will not continue receiving notifications.
Managing Devices
Within a profile, you can add and manage individual devices. When you tap Add device, you only need to provide a name. Once submitted, the device is created, and you are redirected to the Device Details.
In the details view, you will find:
- Device ID: The unique identifier used to target this specific device via the API.
- Onboarding Credentials: While a device is not yet linked, you will see the Onboarding Token and Onboarding URL. These must be shared with the person who will be using the device to complete the registration process (see Onboarding devices).
- Device Management: You can rename or delete a specific device. Deleting a device will instantly sign it out and prevent it from receiving further notifications.
Understanding Device Statuses
A device moves through three distinct states during its lifecycle:
- Pending onboarding: The device has been created in the system but hasn't been linked to a physical phone yet. Use the token or URL to enroll it.
- Enrolled: The device is active and receiving notifications. The onboarding credentials disappear at this stage as they are single-use.
- Unregistered: The user has gracefully signed out of the app. This "releases" the device, and a new onboarding token/URL is automatically generated so it can be registered again if needed.
Note: If a device is uninstalled without signing out, it will remain "Enrolled." You must manually delete it and create a new device to enroll it again.
Onboarding devices
The onboarding flow is the primary method for adding secondary users—such as family members, employees, or teammates—to your Pulsetray account. This process allows them to receive notifications on their own devices without granting them administrative control over your account.
Step-by-Step Onboarding
- Create the device: In your app, go to Settings > Profiles and devices. Select an existing profile or create a new one, then tap Add device.
- Get the Credentials: Once the device is created, you will be redirected to the device details screen where you will see an Onboarding Token and an Onboarding URL.
- Share with the Recipient: Send either the token or the URL to the person you are inviting.
- Register on the Target Device: The recipient must download the Pulsetray app and select Onboard device on the welcome screen to complete the link.
Using the Onboarding Token vs URL
Pulsetray provides two convenient ways to complete the registration on the target device. Both methods are equally secure, but offer different user experiences:
- Onboarding Token: The recipient opens the app, taps "Onboard device," and manually enters the token you provided.
- Onboarding URL: This is the "one-tap" method. When the recipient clicks the Onboarding URL on their phone, the Pulsetray app will launch automatically with the token already pre-filled. They simply need to tap Continue.
Enrollment Confirmation
Once the recipient completes the steps on their device:
- The device status in your management screen will change from Pending onboarding to Enrolled.
- The onboarding token and URL will immediately expire and disappear from the dashboard to ensure it cannot be used again.
- The device is now ready to receive notifications targeted to its specific Device ID, its Profile, or the entire Account.
If you are looking to register your own device with full administrative permissions instead, please refer to the Authentication section for details.
API keys
To send notifications via our Public API, you must authenticate your requests using an API Key. You can generate multiple keys to separate your integrations (e.g., one for Home Assistant and another for your GitHub Actions).
To manage your keys, navigate to Settings > API keys. From this screen, you can view your active keys, create more keys, or revoke them if they are no longer needed.
Creating a New Key
Tap the Add API key button and provide a descriptive name to help you identify where the key will be used. Once you submit the form, you will be redirected to the Key Details screen.
⚠️ Critical: One-Time Token Visibility
For your security, the API Token is only displayed once, immediately after creation.
You must copy and store this token securely now. Once you leave the details screen, the token will be hidden forever. If you lose a token, you will need to delete the key and generate a new one.
Using the Token
The token you generated is a Bearer Token. Every request made to the Pulsetray API must include this token in the Authorization header.
For detailed examples on how to format your API requests and where to place this token, see the API Authentication section.
Revocation and Expiry
- No Expiration: By default, Pulsetray API keys never expire. They remain valid until you manually delete them.
- Instant Revocation: If a key is compromised or no longer in use, you can delete it from the management screen. This action instantly invalidates the token; any scripts or services using that key will immediately receive an authentication error.
Plan and quotas
Pulsetray offers three distinct tiers—Free, Starter, and Pro—designed to scale with your notification needs. Each plan includes specific limits on usage and storage.
Plan Comparison
| Limit | Free | Starter | Pro |
|---|---|---|---|
| Notifications | 100 / mo | 1,000 / mo | 20,000 / mo |
| Profiles | 3 total | 10 total | 30 total |
| Devices | 3 total | 10 total | 50 total |
| Sources | 3 total | 10 total | 30 total |
| Categories | 10 total | 50 total | 100 total |
| API keys | 3 total | 5 total | 10 total |
| Media storage | 400 MB | 5 GB | 20 GB |
| History retention | 60 days | 365 days | 365 days |
| Rate limit | 60 req/min | 500 req/min | 1,000 req/min |
Understanding Quotas
Limits are divided into two types: Recurring and Absolute.
- Recurring Quotas (Monthly): The Notifications count resets at the start of every billing period. For Free users, this period is a rolling 30-day window based on account activity.
- Absolute Quotas (Total): Limits for Devices, API Keys, Profiles, Sources, and Categories are total counts across your entire account. For example, if your plan allows 10 categories, that is the total sum allowed across all Sources.
Subscription Lifecycle
You can manage your plan under Settings > Account (clicking on your name). Subscriptions are billed monthly and provide the following management options:
- Upgrading: Upgrades take effect immediately, providing you with higher limits instantly. If you upgrade from Free to a paid plan, you will also get fresh quotas.
- Cancellations: When you cancel a paid plan, it remains active until the end of the current billing period. You can Reactivate the plan at any time before the period ends. Upgrading to a plan will reactivate the plan automatically.
- Renewal Failures: If a subscription cannot be renewed (e.g., expired credit card), the account is automatically downgraded to the Free plan. No data is deleted during this process.
Important: Exceeding Limits after Downgrade
If you downgrade to a plan with lower limits than your current usage (e.g., having 10 devices while the Free plan only allows 2), your existing data will remain intact.
However, the public API will not allow sending new notifications until you delete the exceeding items to match your new plan's limits.
Storage and Retention
Pulsetray manages your account storage automatically through the retention window of your plan, but you also have manual control.
- Automatic Retention: Notifications older than your plan's retention limit (e.g., 60 days) are automatically deleted. When a notification with media is deleted, the associated storage space is instantly freed.
- Manual Cleanup: If you reach your Media Storage limit, you can manually delete old notifications containing images or videos to free up space for new media.
Authentication
Our API is as friendly as it can be. There is no complex authentication logic or refresh token flows. Just add an Authorization header to all API requests and that's it.
How it Works
All you need to do is generate an API Key within the app (see API keys). This key provides a unique Token that acts as your permanent password for the API.
Then, add the token with the following Authorization header to all API requests:
Authorization: Bearer [YOUR_TOKEN]Key Characteristics
To keep your integrations simple, tokens feature:
- No Expiration: Once generated, your token stays valid forever. You don't have to worry about your scripts breaking because a session expired.
- Stateless: You don't need to "log in" to the API. Every request is independent and authenticated by the header alone.
Sending notifications
POST https://api.pulsetray.com/v1/notifications
This endpoint sends a notification. It supports text, media attachments, and platform-specific configurations for Android an iOS.
Request Formats
The API accepts two primary Content-Type headers:
- application/json: Used for standard text-based notifications or when sending media via Base64.
- multipart/form-data: Required when attaching binary files (images/videos) directly to the request.
Important: If using multipart/form-data, any Object or Array fields (like targets, androidConfig, or apnsConfig) must be sent as JSON-stringified strings.
Parameters
All fields below are optional unless otherwise specified.
| Field | Type | Description |
|---|---|---|
title | String | Title (Max 255 chars). Required if body is not provided. |
body | String | The main message. Supports \n for new lines. Required if title is not provided. |
targets | Array | Specific recipients. See Targets below. |
source | String | Code of the source (e.g., home-assistant). Required if category is provided. |
category | String | Code of the category within the specified source. |
url | String | URL to display in the notification. Supports https://, tel:, sms:, and mailto:. |
androidConfig | Object | Android-specific delivery settings. |
apnsConfig | Object | iOS-specific delivery settings. |
sendPush | Boolean | Default: true. Set to false to skip the push notification and only save to the notifications tray. |
showMediaInPush | Boolean | Default: true. Set to false to hide images from the push notification. |
media | Binary attachment | Only when Content-Type: multipart/form-data. See Handling Attachments below. |
pushMedia | Binary attachment | Only when Content-Type: multipart/form-data. See Handling Attachments below. |
mediaBase64 | String | To attach media using Base64. See Handling Attachments below. |
pushMediaBase64 | String | To attach push media using Base64. See Handling Attachments below. |
Targets
The targets array allows you to control who receives the notification. If this array is null or empty, the notification is sent to the entire account.
- Profile IDs (
prfxxxx): Targets all devices currently or futurely registered under that profile. - Device IDs (
devxxxx): Targets that specific piece of hardware only.
Example: "targets": ["prf123", "dev456"] will send the alert to all devices in Profile prf123 AND specifically to Device dev456.
Handling Attachments
This endpoint supports attaching media, such as images, GIFs and videos. You can attach these files directly via Multipart binary uploads or as Base64 strings.
Due to platform limitations, push notifications only allow sending images. See the size limits and supported formats below.
File Size Limits and Supported Formats
| Field | Type | JPG/JPEG/PNG | MP4 | GIF | Limit |
|---|---|---|---|---|---|
media | Multipart binary | ✅ | ✅ | ✅ | 200 MB |
pushMedia | Multipart binary | ✅ | ❌ | ❌ | 10 MB |
mediaBase64 | Base64 string | ✅ | ✅ | ✅ | 30 MB |
pushMediaBase64 | Base64 string | ✅ | ❌ | ❌ | 10 MB |
Attaching Video
When attaching a video, Pulsetray will generate a thumbnail automatically and send it in the push notification. To prevent this behavior you can set showMediaInPush = false
Media vs. Push Media
While you can simply send media and let Pulsetray handle the rest, you have the option to split how media is displayed:
- media: The primary file. It is shown in the Notifications Tray within the app and also used for the Push Notification by default.
- pushMedia: This overrides the image shown in the push notification.
Common Use Cases
- Standard: Send
mediaonly. It appears in the tray and the push notification. - Override: Send
media(a 1-minute video for the tray) andpushMedia(a small thumbnail image for the push notification). - Push-only: Send
pushMediaonly. The media will appear in the push notification but will not be saved in the app tray history.
Attachment Priority
If you provide both binary and Base64 versions of the same attachment, the binary file takes precedence.
- If
mediais present,mediaBase64is ignored. - If
pushMediais present,pushMediaBase64is ignored.
Instant Delivery vs. Media Uploads
When you call this endpoint with attachments, the API waits for the upload to complete before sending the notification. For large videos or slow connections, this can cause a delay of several seconds.
Pro Tip: For instant delivery, send the notification without media first. This triggers the push notification immediately. You can then use the Attaching media endpoint (see below) to add the files to the notification tray asynchronously.
Advanced configuration
This endpoint exposes platform-specific configurations from FCM, so that you can configure the behavior of the notification in both Android and iOS: androidConfig and apnsConfig.
androidConfig (Object)
priorityenumnormal or high. Use "high" for instant delivery.
ttlSecondsnumberHow long (in seconds) the message stays in FCM storage if the device is offline.
tagstringIdentifier used to replace existing notifications in the drawer.
stickybooleanSet to
trueto prevent the notification from being dismissed on click.
apnsConfig (Object)
prioritynumberDelivery priority: 1, 5, or 10 (recommended).
collapseIdstringAn identifier used to merge multiple alerts into a single notification.
expirationUtcnumberUnix epoch timestamp (seconds) at which the notification is no longer valid.
Battery Optimization & Instant Delivery
For battery optimization, Android and iOS are very strict with notification delivery when the phone is not in use. To make sure notifications are received instantly even when the screen is off, you should explicitly set the priority in your config objects:
- Android:
androidConfig.priority="high" - iOS:
apnsConfig.priority=10
Code Examples
JSON Request, simplest notification
curl -X POST https://api.pulsetray.com/v1/notifications \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Movement detected"
}'JSON Request, more information
curl -X POST https://api.pulsetray.com/v1/notifications \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Laundry Done",
"body": "The washing machine has finished.",
"source": "home-assistant",
"category": "laundry",
"androidConfig": { "priority": "high" }
}'Multipart Request with Media
curl -X POST https://api.pulsetray.com/v1/notifications \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "title=Security Alert" \
-F "media=@/path/to/snapshot.jpg" \
-F "targets=[\"prfabc123\"]" \
-F "androidConfig={\"priority\":\"high\"}"Response Schema
All successful requests return a 200 OK status code with a JSON object containing the unique identifier for your notification.
{
"msg": "success",
"data": {
"publicId": "not2FxL9kZCvCyq8O9NH",
"title": "Security Alert",
"body": "Movement detected on Garage Camera!",
"url": "https://myhome.duckdns.org",
"isShowMediaInPush": true,
"isSendPush": true,
"createdAt": "2026-03-31 07:09:29.615",
"mediaFile": {
"originalFilename": "PXL_20260326_042304215.jpg"
}
}
}Error Responses
If a request fails, Pulsetray returns a standard error object and a relevant HTTP status code (e.g., 401 or 403 for auth issues, 400 for validation errors, 500 for server errors).
{
"msg": "Invalid credentials",
"data": null
}Attaching media
PUT https://api.pulsetray.com/v1/notifications/{publicId}/media
Use this endpoint to add media to an existing notification. This is especially useful for the asynchronous workflow: sending an instant text-only notification first, then uploading heavy media (like 4K snapshots or videos) afterward.
Request Formats
The API accepts two formats for media uploads:
- application/json: Use this if you are sending the file as a Base64 string.
- multipart/form-data: Use this if you are attaching the binary file directly.
Note: {publicId} in the URL must be replaced with the publicId received from the initial Sending notifications response.
Parameters
One of the following fields is required. Formats allowed are jpg, jpeg, png, mp4, gif
| Field | Type | Limit | Description |
|---|---|---|---|
media | Binary | 200 MB | The file to attach. Only available via multipart/form-data. |
mediaBase64 | String | 30 MB | The file encoded as a Base64 string. Only available via application/json. |
Attaching Video
When attaching a video, Pulsetray will generate a thumbnail automatically and send it in the push notification. To prevent this behavior you can set showMediaInPush = false
Attachment Priority
If both media and mediaBase64 are provided in a multipart request, the binary file (media) takes precedence and the Base64 string will be ignored.
Code Examples
Multipart Request (Binary File)
curl -X PUT https://api.pulsetray.com/v1/notifications/not2FxL9kZCvCyq8O9NH/media \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "media=@/path/to/video.mp4"JSON Request (Base64 String)
curl -X PUT https://api.pulsetray.com/v1/notifications/not2FxL9kZCvCyq8O9NH/media \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"mediaBase64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=="
}'Response Schema
Successful updates return a 200 OK status. The response confirms the media has been attached to the notification tray.
{
"msg": "success",
"data": {
"publicId": "not2FxL9kZCvCyq8O9NH",
"title": "Security Alert",
"body": "Movement detected on Garage Camera!",
"url": "https://myhome.duckdns.org",
"isShowMediaInPush": true,
"isSendPush": true,
"createdAt": "2026-03-31 07:09:29.615",
"mediaFile": {
"originalFilename": "PXL_20260326_042304215.jpg"
}
}
}Getting notifications
GET https://api.pulsetray.com/v1/notifications
Retrieves a paginated list of all notifications sent to your account, sorted chronologically with the newest alerts at the top.
Note: This endpoint only returns notifications created via the POST endpoint. Built-in system notifications (such as new sign-ins or device registrations) are excluded from these results.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | Number | 1 | The page number to retrieve. |
limit | Number | 50 | Number of items per page. Maximum: 100. |
Code Example
# Get the 10 most recent notifications
curl -X GET "https://api.pulsetray.com/v1/notifications?page=1&limit=10" \
-H "Authorization: Bearer YOUR_TOKEN"Response Schema
The response returns a data array containing notification objects. If no notifications are found for the requested page, the array will be empty.
{
"msg": "success",
"data": [
{
"publicId": "not2FxL9kZCvCyq8O9NH",
"title": "Security Alert",
"body": "Movement detected on Garage Camera!",
"url": "https://myhome.duckdns.org",
"isShowMediaInPush": true,
"isSendPush": true,
"createdAt": "2026-03-31 07:09:29.615",
"mediaFile": {
"originalFilename": "PXL_20260326_042304215.jpg",
"url": "https://cdn.pulsetray.com/media/xyz.jpg"
}
},
{
"publicId": "notA1B2C3D4E5F6G7H8",
"title": "Laundry Done",
"body": "Washing machine cycle complete.",
"url": null,
"isShowMediaInPush": false,
"isSendPush": true,
"createdAt": "2026-03-30 18:45:12.000",
"mediaFile": null
}
]
}Getting notification details
GET https://api.pulsetray.com/v1/notifications/{publicId}
Retrieves details of a specific notification, including the intended targets and the delivery status of the push notification for each device.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
publicId | String | The unique ID of the notification (e.g., not3CG9F...). |
Understanding "Targets" and "Deliveries"
The targets array returns the IDs of the entities this notification was sent to.
- Account: If you sent the notification without specifying targets, the response will contain your Account ID (e.g.,
accxxxx). - Specific Targets: If you targeted specific profiles or devices, those IDs will be listed here.
The deliveries array returns the IDs of the devices targeted with a push notification using FCM.
This list is generated even if you sent the notification without specifying targets. If you sent a notification to your entire account, the deliveries array will reflect every device registered to that account at the time of sending, showing exactly who was reached.
Response Schema
{
"msg": "Success",
"data": {
"publicId": "not3CG9FhFeozItCzALy",
"title": "Security Alert",
"body": null,
"url": null,
"isShowMediaInPush": true,
"isSendPush": true,
"createdAt": "2026-03-31 07:09:29.615",
"mediaFile": {
"originalFilename": "PXL_20260326_042304215.jpg"
},
"targets": [
"acc6zlATSSZ3fDHh6gB4"
],
"deliveries": [
{
"device": {
"publicId": "devDbG9zPJZ5e5jjj5gJ"
},
"status": "not-delivered",
"reason": "not-onboarded"
},
{
"device": {
"publicId": "devkAvokuebswmFSaPyk"
},
"status": "delivered",
"reason": null
}
]
}
}Delivery Statuses
Possible values for delivery status (deliveries.status). More statuses might be added in the future.
| Status | Description |
|---|---|
pending | The push notification is pending |
delivered | The push notification was sent successfully to FCM |
failed | Request to FCM failed |
not-delivered | Push notification not delivered (more in reason) |
Delivery Reasons
When a delivery failed deliveries.reason will contain the reason why it failed. More reasons might be added in the future.
| Reason | Description |
|---|---|
fcm-failed | The API call to FCM failed |
user-preference | User cannot receive push notifications (no push notifications permission, user settings, etc.). |
not-onboarded | Device is not yet onboarded |
no-fcm-token | Device has no FCM token assigned |
invalid-fcm-token | FCM reported that the token is invalid |
fcm-token-not-registered | FCM reported that the token is not registered |
unknown | Unknown reason |
Errors and limits
Pulsetray uses a standardized response format for all API endpoints to make integration as predictable as possible.
Response Format
Every response from our API is a JSON object containing a msg and a data field.
- Success: The
msgwill be "Success" (or "success") anddatawill contain the requested information. - Error: If something goes wrong,
datawill benulland themsgfield will contain a human-readable description of the error.
HTTP Status Codes
We use standard HTTP status codes to indicate the success or failure of an API request. A 200 OK always indicates a successful transaction.
| Code | Description |
|---|---|
400 | Bad Request. Often due to missing parameters or malformed JSON. |
401 | Unauthorized. Your API key is missing or invalid. |
403 | Forbidden. You have exceeded your plan’s monthly notification quota. |
429 | Too Many Requests. You are hitting the rate limit (be nice to the API!). |
5xx | Server Error. Something went wrong on our end. Reach out if this persists. |
Rate Limiting & Usage
To ensure high availability for all users, we implement a fair-use rate limit (requests per minute). We just ask you to be nice with the API :).
If you are sending a high volume of alerts in a short window, please consider batching your notifications or introducing a small delay between requests to avoid receiving 429 errors.
Integrations & Samples
Pulsetray is designed to fit seamlessly into your existing workflows. Whether you want to add notifications to your smart home or your production servers, these snippets will get you up and running in minutes.
Home Assistant (Core)
The easiest way to integrate Pulsetray into Home Assistant is using a rest_command. This allows you to call Pulsetray as a native service within your automations and scripts.
1. Configuration
Add the following to your configuration.yaml file:
rest_command:
pulsetray:
url: "https://api.pulsetray.com/v1/notifications"
method: post
content_type: "application/json"
headers:
Authorization: "Bearer YOUR_TOKEN"
payload: "{{ json_body | to_json }}"Make sure to restart your HA instance.
2. Example Automation
Call the service from an automation to send a notification:
action: rest_command.pulsetray
data:
json_body:
title: "Security Alert"
body: "The front door was opened at {{ now().strftime('%H:%M') }}"
source: "home-assistant"
androidConfig:
priority: "high"Pro-Tip: Hybrid approach
If you prefer the Home Assistant Companion app for push notifications but want a persistent, searchable history in Pulsetray, you can use both systems together.
By setting the sendPush parameter to false in your Pulsetray API call, our system will skip the push notification and only save the record to your tray.
This allows you to continue receiving immediate pings via Home Assistant while Pulsetray maintains an organized archive of every event for you to review at any time.
Home Assistant (Node-RED)
If you prefer to use Node-RED, we recommend you create a Function Node to build the request, followed by a Exec Node to execute it. This way, it becomes really easy to manipulate the request and add attachments if needed.
Function Node Code:
const token = "YOUR_API_TOKEN";
const url = "https://api.pulsetray.com/v1/notifications";
// Construct the multipart curl command
msg.payload = `curl -X POST \
-H "Authorization: Bearer ${token}" \
-F "title=Camera Alert" \
-F "body=Motion detected at ${msg.time}" \
-F "androidConfig={\\"priority\\":\\"high\\"}" \
${url}`;
// If you need to add media, you can do so easily with:
// -F "media=@/path/to/snapshot.mp4"
return msg;Exec Node
Connect the Function Node to the Exec Node, and make sure to set Append: msg.payload.
Why use curl in Node-RED?
While Node-RED has a native HTTP node, handling multipart/form-data with file buffers can be cumbersome. Using curl via an Exec Node is significantly faster to implement for binary media like videos or snapshots.
cURL
cURL is the standard for testing and simple scripting. It is widely available on Linux, macOS, and Windows, making it ideal for server-side alerts, cron jobs, and CI/CD pipelines.
1. Simple JSON Notification
Best for quick, text-only alerts.
curl -X POST https://api.pulsetray.com/v1/notifications \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Server Alert",
"body": "Daily backup completed successfully."
}'2. Structured JSON (Categorized)
Includes a source, category, and delivery priority for better organization.
curl -X POST https://api.pulsetray.com/v1/notifications \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "System Monitor",
"body": "CPU usage on Production-01 is above 90%.",
"source": "monitoring",
"category": "critical",
"androidConfig": { "priority": "high" }
}'3. Multipart Upload (With Media)
To send files, use the -F (form) flag. Note that nested objects like androidConfig or targets must be JSON-stringified.
curl -X POST https://api.pulsetray.com/v1/notifications \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "title=Security Camera" \
-F "body=Motion detected in the driveway." \
-F "media=@/home/user/snapshots/latest.jpg" \
-F "targets=[\"prfabc123\"]" \
-F "androidConfig={\"priority\":\"high\"}"GitHub Actions
Integrating Pulsetray into your CI/CD pipeline allows you to receive instant alerts for deployment successes, build failures, or security vulnerabilities. Since GitHub runners include curl by default, you can trigger notifications using a simple shell step.
Security Tip: Use Secrets
Never hardcode your API token in your workflow files. Instead, store it as a Repository Secret (Settings > Secrets and variables > Actions) and reference it as ${{ secrets.PULSETRAY_TOKEN }}.
Example Workflow Step:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy Application
run: ./deploy.sh # Your deployment logic
- name: Notify Pulsetray on Success
if: success()
run: |
curl -X POST https://api.pulsetray.com/v1/notifications \
-H "Authorization: Bearer ${{ secrets.PULSETRAY_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{
"title": "Deployment Successful",
"body": "Build ${{ github.run_number }} has been deployed to production.",
"source": "github-actions",
"category": "deployments",
"url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
"androidConfig": { "priority": "high" }
}'Example: Notify on Failure
You can use the if: failure() conditional to ensure you are alerted only when a critical step in your pipeline fails.
- name: Notify Pulsetray on Failure
if: failure()
run: |
curl -X POST https://api.pulsetray.com/v1/notifications \
-H "Authorization: Bearer ${{ secrets.PULSETRAY_TOKEN }}" \
-d '{
"title": "Deployment Failed",
"body": "The build for ${{ github.repository }} failed at the testing stage.",
"source": "github-actions",
"category": "errors",
"androidConfig": { "priority": "high" }
}'Node.js
Whether you are building a SaaS backend, a Discord bot, or a custom monitoring script, Pulsetray integrates easily using axios or the native fetch API.
1. Simple JSON Notification
Best for standard text-based alerts from your server.
const axios = require('axios');
const sendNotification = async () => {
try {
const response = await axios.post('https://api.pulsetray.com/v1/notifications', {
title: "Script",
body: "Database backup completed successfully.",
source: "script",
androidConfig: { priority: "high" }
}, {
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json'
}
});
console.log('Success:', response.data);
} catch (error) {
console.error('Error:', error.response?.data || error.message);
}
};
sendNotification();2. Multipart Upload with Media
To send files in Node.js, we recommend using form-data. Note that nested objects must be stringified.
const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');
const sendMediaNotification = async () => {
const form = new FormData();
// Basic fields
form.append('title', 'Security Alert');
form.append('body', 'Motion detected in the office.');
// File attachment
form.append('media', fs.createReadStream('./snapshot.jpg'));
// Objects must be JSON stringified in multipart requests
form.append('targets', JSON.stringify(['prfabc123']));
form.append('androidConfig', JSON.stringify({ priority: 'high' }));
try {
const response = await axios.post('https://api.pulsetray.com/v1/notifications', form, {
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN',
...form.getHeaders()
}
});
console.log('Notification sent:', response.data.publicId);
} catch (error) {
console.error('Upload failed:', error.response?.data || error.message);
}
};
sendMediaNotification();Pro-Tip: Async Attachments
If your notification has a large media file, you can send the notification instantly without media first, and then use the PUT endpoint to attach media to be available in the notifications tray.
Python
Python is ideal for automation scripts, IoT projects, and data processing alerts. We recommend using the requests library for a clean integration.
Installation
If you don't have it already, you can install the requests library via pip:
pip install requests1. Simple JSON Notification
Standard text-based alert using the json parameter.
import requests
url = "https://api.pulsetray.com/v1/notifications"
headers = {
"Authorization": "Bearer YOUR_API_TOKEN"
}
payload = {
"title": "Backup",
"body": "Daily database backup finished.",
"source": "python-script",
"androidConfig": { "priority": "high" }
}
response = requests.post(url, json=payload, headers=headers)
if response.status_code == 200:
print("Notification sent:", response.json().get("data").get("publicId"))
else:
print("Error:", response.json().get("msg"))2. Multipart Upload with Media
When sending files, use the files parameter. Any object or array fields must be JSON stringified before being added to the data dictionary.
import requests
import json
url = "https://api.pulsetray.com/v1/notifications"
headers = {
"Authorization": "Bearer YOUR_API_TOKEN"
}
# Nested objects must be stringified for multipart requests
data = {
"title": "Camera Alert",
"body": "Motion detected in the Backyard.",
"targets": json.dumps(["prfabc123"]),
"androidConfig": json.dumps({ "priority": "high" })
}
# Attach the binary file
files = {
"media": ("snapshot.jpg", open("snapshot.jpg", "rb"), "image/jpeg")
}
response = requests.post(url, data=data, files=files, headers=headers)
print(response.json())Go
Go is perfect for building lightweight monitoring agents or high-throughput backend services. These examples use the standard net/http.
1. Simple JSON Notification
Using encoding/json to dispatch a standard text alert.
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
url := "https://api.pulsetray.com/v1/notifications"
token := "YOUR_API_TOKEN"
payload := map[string]interface{}{
"title": "System Alert",
"body": "Service 'auth-provider' restarted.",
"androidConfig": map[string]string{
"priority": "high",
},
}
jsonPayload, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonPayload))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}2. Multipart Upload with Media
Sending files in Go requires the mime/multipart package. Remember to JSON-stringify complex objects before adding them to the form.
package main
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
)
func main() {
url := "https://api.pulsetray.com/v1/notifications"
token := "YOUR_API_TOKEN"
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
// Basic fields
_ = writer.WriteField("title", "Security Snapshot")
// JSON-stringify objects/arrays manually for multipart
_ = writer.WriteField("targets", `["prfabc123"]`)
_ = writer.WriteField("androidConfig", `{"priority":"high"}`)
// Attach the file
file, _ := os.Open("snapshot.jpg")
defer file.Close()
part, _ := writer.CreateFormFile("media", "snapshot.jpg")
_, _ = io.Copy(part, file)
writer.Close()
req, _ := http.NewRequest("POST", url, body)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", writer.FormDataContentType())
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Upload Status:", resp.Status)
}PHP
Pulsetray can be integrated into any PHP environment using the native curl extension. These examples are compatible with PHP 7.4 through PHP 8.x.
1. Simple JSON Notification
Ideal for text alerts from contact forms, CRON jobs, or CMS plugins.
<?php
$url = "https://api.pulsetray.com/v1/notifications";
$token = "YOUR_API_TOKEN";
$payload = [
"title" => "Website Alert",
"body" => "New contact form submission received.",
"source" => "wordpress-site",
"androidConfig" => [ "priority" => "high" ]
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $token",
"Content-Type: application/json"
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "Status Code: $httpCode\n";
echo "Response: $response\n";
?>2. Multipart Upload with Media
To send files, use the CURLFile class. Remember that nested arrays/objects like targets must be JSON encoded before being added to the post fields.
<?php
$url = "https://api.pulsetray.com/v1/notifications";
$token = "YOUR_API_TOKEN";
// Complex objects must be stringified for multipart requests
$data = [
"title" => "Security Snapshot",
"body" => "Motion detected on Front Camera.",
"targets" => json_encode(["prfabc123"]),
"androidConfig" => json_encode(["priority" => "high"]),
"media" => new CURLFile('snapshot.jpg', 'image/jpeg', 'snapshot.jpg')
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $token"
]);
$response = curl_exec($ch);
curl_close($ch);
print_r(json_decode($response, true));
?>