One REST API sends push notifications to Android, iOS and web browsers. Your backend needs three things from App settings in the dashboard:
| Where to find it | |
|---|---|
| Base URL | https://pushix.in/api/v1 |
| App ID | App settings → Keys |
| REST API key | App settings → Keys |
Both URL styles work: /api/v1/notifications and /notifications are the same endpoint.
Authentication
Endpoints that send or read data need the app's REST API key in the Authorization header. Any of these forms is accepted:
Authorization: Basic YOUR_REST_API_KEY
Authorization: Key YOUR_REST_API_KEY
Authorization: Bearer YOUR_REST_API_KEYDevice endpoints used by the apps and the web SDK (create device, update device, sessions, click tracking, in-app messages) only need app_id. Never put the REST API key inside your app or website.
Errors
Errors come as a JSON list with an HTTP status of 400, 402, 403, 404 or 500:
{ "errors": ["You must include which players, segments, or filters to send this notification to."] }A send that matches nobody returns HTTP 200 with an explanation:
{ "id": "", "recipients": 0, "external_id": null, "errors": ["All included players are not subscribed"] }Quick start
curl -X POST https://pushix.in/api/v1/notifications \
-H "Content-Type: application/json" \
-H "Authorization: Basic YOUR_REST_API_KEY" \
-d '{
"app_id": "YOUR_APP_ID",
"included_segments": ["Subscribed Users"],
"headings": {"en": "Bonus unlocked"},
"contents": {"en": "Your cart is waiting. Complete your order today and get free delivery."},
"data": {"screen": "orders"}
}'Response:
{ "id": "b98881cc-1e94-4366-bbd9-db8f3429292b", "recipients": 1250, "external_id": null }That one call reaches every subscribed Android phone, iPhone and browser. The same call from PHP:
$payload = [
'app_id' => 'YOUR_APP_ID',
'included_segments' => ['Subscribed Users'],
'headings' => ['en' => 'Bonus unlocked'],
'contents' => ['en' => 'Your cart is waiting.'],
'data' => ['screen' => 'orders'],
];
$ch = curl_init('https://pushix.in/api/v1/notifications');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: Basic YOUR_REST_API_KEY'],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);A ready-made helper class is in libraries/php/PushixClient.php.
Notifications
Create notification
POST /api/v1/notifications — REST API key required.
Targeting
Use exactly one of these: segments, filters, or ids. They cannot be combined.
| Field | Type | Description |
|---|---|---|
included_segments | array | Segment names, e.g. ["Subscribed Users"] or your own segments. |
excluded_segments | array | Segments to leave out (only with included_segments). |
filters | array | Filter rules, see Filters below. |
include_external_user_ids | array | Your own user ids (set with Pushix.login). |
include_aliases | object | {"external_id": ["u1", "u2"]}, or device ids with {"subscription_id": [...]}. |
include_player_ids | array | Pushix player ids (device ids). |
include_subscription_ids | array | Same as include_player_ids. |
include_android_reg_ids | array | Raw FCM tokens of devices already registered with Pushix. |
target_channel | string | Optional. Only "push" is supported. |
Id lists accept up to 10,000 entries per request.
Platforms
By default a message goes to every platform the app has set up. Narrow it with platform flags or a plain list:
| Field | Description |
|---|---|
isAndroid, isIos, isAnyWeb | true on some flags: only those platforms. false on a flag: everything except that platform. |
isChromeWeb, isFirefox, isSafari, isEdge, isChrome | Accepted for compatibility; all mean "web". |
platforms | Pushix's own form: ["android", "ios", "web"]. Wins over the flags. |
Silent messages (content_available) never go to browsers, because browsers must always show something.
Content
| Field | Type | Description |
|---|---|---|
contents | object | Message text per language: {"en": "Hello", "hi": "नमस्ते"}. Required unless content_available is true or a template supplies it. |
headings | object | Title per language. |
subtitle | object | iOS subtitle per language (the line under the title). |
data | object | Extra data delivered as additionalData. |
url / app_url | string | Link or deep link for the apps. A https:// link is also used for browsers unless web_url is set. |
big_picture | string | Picture URL: large on Android and the web, a thumbnail on iPhone (2:1 works best). |
large_icon | string | Android: image URL (or drawable name) shown on the right. |
buttons | array | Up to 3: [{"id": "claim", "text": "Claim", "url": "https://…"}]. Shown on Android, on iPhone (apps with the Pushix notification service extension, or your own ios_category) and in browsers (the first 2 unless web_buttons is set). |
content_available | boolean | Data-only message with no visible notification (needs data, no contents). Android and iOS only. |
template_id | string | Start from a saved template; any field you send overrides the template's. |
name | string | Internal name shown in the dashboard. |
Each device gets the language that matches its system language (hi, bn, ...), falling back to en.
Personalise text with the device's tags:
"contents": {"en": "Hi {{ name | default: 'friend' }}, you have {{ points }} reward points"}{{ external_id }} and {{ player_id }} also work.
Android
| Field | Type | Description |
|---|---|---|
android_channel_id | string | Notification channel. Default: the app's default (pushix_default, created by the SDK with high importance). existing_android_channel_id is accepted too. |
small_icon | string | Drawable name in the app, e.g. ic_stat_notification. |
android_accent_color | string | FF9976D2 (ARGB) or #9976D2. |
android_sound | string | Sound file name in res/raw without extension. On Android 8+ the channel decides the sound. |
android_visibility | integer | Lock screen: 1 public, 0 private, -1 secret. |
android_group | string | Group notifications together. |
iOS
iPhone messages go through Firebase Cloud Messaging with an APNs payload, so the app needs the Firebase key plus an APNs key uploaded to Firebase (see the iOS SDK or Flutter SDK guide).
| Field | Type | Description |
|---|---|---|
ios_sound | string | A sound file in the app bundle, "default", or "nil" for silent. |
ios_badgeType | string | None (default), Increase or SetTo. |
ios_badgeCount | integer | The amount to add or set. Pushix keeps each device's count and resets it when the app opens. |
ios_attachments | object | {"id": "https://…/pic.jpg"}; the first URL is used as the picture. big_picture also works. |
thread_id | string | Groups notifications in Notification Center. |
ios_category | string | A notification category your app registers, for custom iOS actions. Not needed for buttons: the iOS SDK's Notification Service Extension shows them. |
ios_interruption_level | string | passive, active, time-sensitive or critical. |
ios_relevance_score | number | 0–1, for the notification summary. |
mutable_content | boolean | Lets your Notification Service Extension change the message. Set automatically when there is a picture. |
Web
| Field | Type | Description |
|---|---|---|
web_url | string | Opened when the notification is clicked in a browser. |
chrome_web_icon / firefox_icon | string | Icon URL. Defaults to the icon in App settings → Web push. |
chrome_web_image | string | Large picture for browsers (defaults to big_picture). |
chrome_web_badge | string | Small monochrome icon for Android Chrome's status bar. |
web_buttons | array | Up to 2: [{"id": "shop", "text": "Shop now", "url": "https://…"}]. |
persist_notification / require_interaction | boolean | Keep it on screen until clicked (desktop Chrome and Edge). |
web_push_topic | string | A newer message with the same topic replaces the older one. |
Delivery
| Field | Type | Description |
|---|---|---|
send_after | string | Schedule: "2026-11-01 09:00:00 GMT+0530", ISO 8601, or unix seconds. |
ttl | integer | Seconds the push service keeps trying if the device is offline. Default 259200 (3 days), max 28 days. |
priority | integer | 10 high (default, shows right away), 5 normal. |
collapse_id | string | A newer message with the same id replaces the older one (all platforms). |
throttle_rate_per_minute | integer | Send at most this many per minute, so your backend is not hit by everyone opening the app at once. |
external_id / idempotency_key | string | Sending the same key twice returns the first notification instead of sending again. |
Response
{ "id": "b98881cc-1e94-4366-bbd9-db8f3429292b", "recipients": 1250, "external_id": null }recipients is the number of subscribed devices that match right now. Sending happens in the background: small sends go out within seconds; large sends are processed in batches of 500 by the worker.
Examples
Send to your own user ids, only on their phones:
{
"app_id": "YOUR_APP_ID",
"include_external_user_ids": ["10234", "10235"],
"isAnyWeb": false,
"headings": {"en": "Order shipped"},
"contents": {"en": "₹250 has been sent to your UPI ID."},
"data": {"screen": "orders", "id": "OD-4821"}
}Users with a tag above a value, or who haven't opened the app in two days:
{
"app_id": "YOUR_APP_ID",
"contents": {"en": "Your daily spin is ready"},
"filters": [
{"field": "tag", "key": "level", "relation": ">", "value": "5"},
{"operator": "OR"},
{"field": "last_session", "relation": ">", "hours_ago": "48"}
]
}Scheduled, with buttons, a picture, an iOS badge and a website link:
{
"app_id": "YOUR_APP_ID",
"included_segments": ["Active Users"],
"excluded_segments": ["Test Users"],
"headings": {"en": "Diwali offer", "hi": "दिवाली ऑफ़र"},
"contents": {"en": "Flash sale: 50% off today only", "hi": "आज ही 50% की छूट"},
"big_picture": "https://yourdomain.com/img/diwali.jpg",
"buttons": [{"id": "open", "text": "Open app"}, {"id": "share", "text": "Share"}],
"ios_badgeType": "Increase", "ios_badgeCount": 1,
"web_url": "https://yourdomain.com/diwali",
"send_after": "2026-11-08 10:00:00 GMT+0530",
"throttle_rate_per_minute": 2000
}From a template, with one value filled in:
{ "app_id": "YOUR_APP_ID", "template_id": "TEMPLATE_ID", "include_external_user_ids": ["10234"] }Data-only message (nothing shown; your app gets it in addSilentDataListener while open):
{ "app_id": "YOUR_APP_ID", "include_external_user_ids": ["10234"], "content_available": true, "data": {"refresh": "balance"} }View notification
GET /api/v1/notifications/{id}?app_id=YOUR_APP_ID — REST API key required.
{
"id": "b98881cc-1e94-4366-bbd9-db8f3429292b",
"app_id": "YOUR_APP_ID",
"status": "completed",
"contents": {"en": "Your cart is waiting."},
"headings": {"en": "Bonus unlocked"},
"data": {"screen": "orders"},
"included_segments": ["Subscribed Users"],
"platforms": ["android", "ios", "web"],
"recipients": 1250,
"successful": 1231,
"failed": 17,
"errored": 2,
"converted": 140,
"remaining": 0,
"queued_at": 1793280000,
"send_after": 1793280000,
"completed_at": 1793280011,
"canceled": false,
"platform_delivery_stats": {
"android": {"successful": 900, "failed": 12, "errored": 2, "converted": 104},
"ios": {"successful": 250, "failed": 3, "errored": 0, "converted": 25},
"web_push": {"successful": 81, "failed": 2, "errored": 0, "converted": 11}
},
"template_id": null,
"error": null
}| Field | Meaning |
|---|---|
status | scheduled, queued, processing, completed, canceled or failed |
successful | Accepted by Firebase or the browser's push service |
failed | The app was uninstalled or the browser unsubscribed; the device is unsubscribed automatically |
errored | Other errors; the first one is in error |
converted | Devices that clicked the notification (counted once each) |
remaining | Not sent yet |
List notifications
GET /api/v1/notifications?app_id=YOUR_APP_ID&limit=50&offset=0 — REST API key required.
limit is at most 50. Add kind=0 for dashboard sends only or kind=1 for API sends only.
{ "total_count": 312, "offset": 0, "limit": 50, "notifications": [ ... ] }Cancel notification
DELETE /api/v1/notifications/{id}?app_id=YOUR_APP_ID — REST API key required.
Cancels a scheduled message, or stops one that is still sending. Returns {"success": true}.
Track open
PUT /api/v1/notifications/{id} — called by the SDKs when a notification is clicked.
{ "app_id": "YOUR_APP_ID", "player_id": "DEVICE_ID", "opened": true, "action_id": "claim" }Templates
Saved messages you can send by id. Create them in the dashboard (composer → Save as template) or here.
| Endpoint | |
|---|---|
POST /api/v1/templates | Create. Body: app_id, name, and any content fields (headings, contents, big_picture, buttons, ios_*, web_*, …). Returns {"id", "name"}. |
GET /api/v1/templates?app_id=…&limit=50&offset=0 | List (names and ids). |
GET /api/v1/templates/{id}?app_id=… | View with content. |
PATCH /api/v1/templates/{id} | Update (app_id in the body). Fields you send replace the saved ones. |
DELETE /api/v1/templates/{id}?app_id=… | Delete. |
All need the REST API key. Send with "template_id" on Create notification.
Filters
Filters are an array. Rules next to each other must all match (AND). {"operator": "OR"} starts a new group.
| Field | Relations | Value |
|---|---|---|
tag | =, !=, >, <, >=, <=, exists, not_exists, time_elapsed_gt, time_elapsed_lt | key and value. > and < compare numbers. time_elapsed_* treat the tag as a unix timestamp and value as seconds. != also matches devices without the tag. |
platform (or device_type) | =, != | android, ios, web (or a list) |
last_session | > (more than N hours ago), < (within N hours) | hours_ago |
first_session | >, < | hours_ago |
session_count | >, <, =, !=, >=, <= | number |
session_time | same as above | seconds spent in the app |
amount_spent | same as above | number |
language | =, !=, exists, not_exists | e.g. hi |
app_version | =, !=, exists, not_exists | e.g. 2.4.0 |
country | =, !=, exists, not_exists | e.g. IN |
device_model | =, !=, exists, not_exists | e.g. Xiaomi Redmi Note 12 |
external_user_id | =, exists, not_exists | user id |
Examples:
[{"field": "tag", "key": "plan", "relation": "=", "value": "gold"}]
[{"field": "platform", "relation": "=", "value": "ios"}, {"field": "last_session", "relation": ">", "hours_ago": "72"}]
[{"field": "external_user_id", "relation": "not_exists"}]Segments
Built-in segments
| Name | Who |
|---|---|
Subscribed Users (also All, Total Subscriptions) | Every device that can receive push |
Active Users | Opened the app in the last 7 days |
Engaged Users | Opened the app 10 or more times |
Inactive Users | Not opened the app for more than 7 days |
Android Users, iOS Users, Web Subscribers | Subscribed devices on that platform |
Test Users | Devices marked as test devices in the dashboard |
Create segment
POST /api/v1/apps/{app_id}/segments — REST API key required.
{ "name": "Gold members", "filters": [{"field": "tag", "key": "plan", "relation": "=", "value": "gold"}] }Returns {"success": true, "id": "SEGMENT_ID"}. Segments are evaluated at send time, so new devices that match are included automatically.
List segments
GET /api/v1/apps/{app_id}/segments?limit=300&offset=0 — REST API key required.
Delete segment
DELETE /api/v1/apps/{app_id}/segments/{segment_id} — REST API key required.
Devices (players)
A device is one app install or one browser. Its id is also called the player id or subscription id.
Add a device
POST /api/v1/players — app_id only. The SDKs call this for you.
| Field | Description |
|---|---|
app_id | Required |
device_type | Required. 1 Android, 0 iOS, 5 web (7, 8 and 17 are also treated as web) |
identifier | FCM token (Android and iOS) or the browser's push endpoint URL (web) |
web_auth, web_p256 | Web only: the subscription's auth and p256dh keys |
notification_types | 1 subscribed, -2 opted out, 0 no permission |
language | e.g. en, hi |
timezone | Offset in seconds, e.g. 19800 for IST |
game_version | App version |
device_model, device_os, country, sdk | Device info |
external_user_id | Your user id |
tags | {"key": "value"} |
session_count, amount_spent, test_type | Optional |
Returns {"success": true, "id": "DEVICE_ID"}. Registering the same token or endpoint again returns the same id, so reinstalls don't create duplicates.
Edit a device
PUT /api/v1/players/{id} — app_id only. Same fields as above. A tag value of "" deletes that tag. "clear_tags": true removes all tags first.
Sessions
POST /api/v1/players/{id}/on_session — counts a new app open, updates last_active and resets the iOS badge count (unless the body sends badge_count, which is then stored as is). Same body as edit.
POST /api/v1/players/{id}/on_focus with {"app_id": "...", "state": "ping", "active_time": 60} adds seconds spent in the app.
View a device
GET /api/v1/players/{id}?app_id=YOUR_APP_ID — REST API key required.
{
"id": "5c6c4b1e-4f0e-4a5e-9a3b-0d3c3c8f2a11",
"identifier": "fcm-token…",
"platform": "android",
"device_type": 1,
"session_count": 14,
"language": "hi",
"timezone": 19800,
"game_version": "2.4.0",
"device_os": "14",
"device_model": "Xiaomi Redmi Note 12",
"tags": {"level": "5"},
"badge_count": 0,
"last_active": 1793280000,
"playtime": 3600,
"amount_spent": 0,
"created_at": 1790000000,
"invalid_identifier": false,
"external_user_id": "10234",
"notification_types": 1,
"subscribed": true
}List devices
GET /api/v1/players?app_id=YOUR_APP_ID&limit=300&offset=0 — REST API key required. Newest first, limit at most 300.
Delete a device
DELETE /api/v1/players/{id}?app_id=YOUR_APP_ID — REST API key required.
Import devices
POST /api/v1/apps/{app_id}/players/import — REST API key required. Up to 2000 per request.
{ "players": [ {"id": "OLD-PLAYER-ID", "identifier": "fcm-token", "device_type": 1, "external_user_id": "10234", "tags": {"level": "5"}} ] }Keeps the original ids. Android rows are imported; iOS and web subscribers register again when they next open your app or site. The dashboard's “Import CSV” button (Subscribers) uses this with a CSV export of your subscribers.
Users (external user id)
Edit tags for a user
PUT /api/v1/apps/{app_id}/users/{external_user_id} — REST API key required. Updates every device of that user.
{ "tags": {"balance": "250", "kyc": "done", "old_tag": ""} }Returns {"success": true, "devices_updated": 2}.
View a user's devices
GET /api/v1/apps/{app_id}/users/{external_user_id} — REST API key required.
SDK endpoints
Called by the Pushix SDKs (Flutter, Android, iOS, React Native, web) with app_id only. Browsers may call them from any website (CORS is open on these paths).
| Endpoint | |
|---|---|
GET /api/v1/apps/{app_id}/web_config | Public web push settings: VAPID public key, prompt texts, whether web in-app messages exist. |
GET /api/v1/apps/{app_id}/in_app_messages?player_id=… | In-app messages this device may see, with its session_count. |
POST /api/v1/in_app_messages/{id}/events | {"app_id", "player_id", "event": "impression" | "click" | "dismiss", "button_id", "platform"} (platform is optional: android, ios or web) |
If the organization's plan doesn't include in-app messages, the in-app list is empty (no error), so apps keep working.
App
GET /api/v1/apps/{app_id} — REST API key required. Returns players (all devices) and messageable_players (subscribed).
What's supported
| Feature | Pushix |
|---|---|
| Android and iOS push (title, subtitle, text, picture, buttons, sounds, badges, deep links, data) | Yes |
| Web push (Chrome, Edge, Firefox, Opera, Safari) with prompts, bell and welcome message | Yes |
| Segments, filters, tags, external user ids, aliases, platform targeting | Yes |
Scheduling (send_after), throttling, TTL, collapse id, idempotency | Yes |
| Multiple languages, tag personalisation | Yes |
Templates (template_id) | Yes |
| A/B tests (2–5 variants, automatic or manual winner) | Yes, from the dashboard |
| Automated messages (welcome, win-back, recurring) | Yes, from the dashboard |
| In-app messages (pop-up, card, banners, full screen; triggers and frequency caps) | Yes, in the Flutter, Android, iOS, React Native and web SDKs |
| Analytics: delivery by platform, clicks, active users, best time to send, CSV exports | Yes |
| Email, SMS, Live Activities | No |
delayed_option timezone / intelligent delivery | No (returns an error) |
| Location filters | No |
Send your first notification
Create an app in Pushix to get your App ID and REST API key, then follow this guide. The free plan needs no card.