Push Notifications API
Send push notifications via your own Firebase project (BYOF). 100% free and unlimited.
Base URL
https://api.moontraze.comPermission Model
| Role | Configure Firebase | Send | Receive |
|---|---|---|---|
| Owner | Yes | Yes | Yes |
| Admin (with permission) | No | Yes | Yes |
| User | No | No | Yes |
Firebase configuration (service account upload) is owner-only. Admins cannot access or modify credentials.
Authentication
All requests require:
Authorization: Bearer <token>
Content-Type: application/json
X-Project-Id: YOUR_PROJECT_ID
X-API-Key: YOUR_API_KEY<token>— JWT from loginX-Project-Id— Your Moontraze project IDX-API-Key— Your project API key (from dashboard)
Firebase Credentials Setup (Owner Only)
Before sending notifications, you must upload your Firebase service account credentials once per project.
Check Status
GET /api/projects/:projectId/firebase-credentials/status
Authorization: Bearer <owner-token>Response:
{
"success": true,
"configured": true,
"firebaseProjectId": "my-app-prod",
"firebaseClientEmail": "firebase-adminsdk-xxxxx@my-app-prod.iam.gserviceaccount.com",
"updatedAt": "2026-09-22T10:30:00.000Z"
}Upload Service Account
Send as multipart/form-data (field name: serviceAccount) OR as raw JSON body.
Multipart (recommended):
POST /api/projects/:projectId/firebase-credentials
Authorization: Bearer <owner-token>
Content-Type: multipart/form-data
serviceAccount: <your-service-account.json>Raw JSON body:
POST /api/projects/:projectId/firebase-credentials
Authorization: Bearer <owner-token>
Content-Type: application/json
{
"type": "service_account",
"project_id": "my-app-prod",
"private_key_id": "abc123",
"private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-xxxxx@my-app-prod.iam.gserviceaccount.com",
"client_id": "1234567890",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-xxxxx%40my-app-prod.iam.gserviceaccount.com"
}Response:
{
"success": true,
"verified": true,
"firebaseProjectId": "my-app-prod",
"firebaseClientEmail": "firebase-adminsdk-xxxxx@my-app-prod.iam.gserviceaccount.com",
"message": "Firebase credentials saved and verified"
}Credentials are encrypted with AES-256-GCM at rest. The private key is never returned by any endpoint.
Verify Credentials
POST /api/projects/:projectId/firebase-credentials/verify
Authorization: Bearer <owner-token>Response:
{
"success": true,
"error": null
}Remove Credentials
DELETE /api/projects/:projectId/firebase-credentials
Authorization: Bearer <owner-token>Response:
{
"success": true,
"message": "Firebase credentials removed. Push notifications disabled."
}Enable FCM for Project (Owner Only)
After credentials are uploaded, enable FCM to start sending.
POST /api/notifications/toggle-fcm
Authorization: Bearer <owner-token>
Content-Type: application/json
{
"projectId": "YOUR_PROJECT_ID",
"enabled": true
}Response:
{
"success": true,
"fcmEnabled": true,
"fcmTopic": "project_YOUR_PROJECT_ID"
}Enabling FCM requires Firebase credentials to be uploaded first. If not, you'll receive
FIREBASE_NOT_CONFIGURED.
Register FCM Token
Called by the client app after obtaining an FCM token.
POST /api/notifications/register
Authorization: Bearer <user-token>
Content-Type: application/json
{
"token": "FCM_TOKEN",
"projectId": "YOUR_PROJECT_ID",
"deviceInfo": {
"platform": "android",
"deviceId": "unique-device-id"
}
}Response:
{
"success": true
}
projectIdis required in the body (also accepted viaX-Project-Idheader).
Unregister FCM Token
Call on logout or token rotation.
POST /api/notifications/unregister
Authorization: Bearer <user-token>
Content-Type: application/json
{
"token": "FCM_TOKEN"
}Response:
{
"success": true
}Send to Project (All Users)
POST /api/notifications/send-to-project/:projectId
Authorization: Bearer <owner-token>
Content-Type: application/json
{
"title": "Hello",
"body": "World",
"data": {
"screen": "home"
}
}Response:
{
"success": true,
"successCount": 145,
"failureCount": 3,
"failedTokens": []
}
failedTokenscontains only tokens Firebase reported as invalid. These are auto-marked inactive in the database.
Send to User
POST /api/notifications/send-to-user/:userId
Authorization: Bearer <owner-token>
Content-Type: application/json
{
"projectId": "YOUR_PROJECT_ID",
"title": "Order Shipped",
"body": "Your order is on the way",
"data": {
"type": "order",
"orderId": "1234",
"deepLink": "myapp://order/1234"
}
}Response:
{
"success": true,
"successCount": 1,
"failureCount": 0
}
projectIdrequired in body forsend-to-user.
Send to Topic
Devices must first subscribe to the topic via the Firebase SDK (subscribeToTopic('premium_users')).
POST /api/notifications/send-to-topic
Authorization: Bearer <owner-token>
Content-Type: application/json
{
"projectId": "YOUR_PROJECT_ID",
"topic": "premium_users",
"title": "Premium Feature",
"body": "Exclusive for premium users",
"data": {
"type": "upgrade"
}
}Response:
{
"success": true,
"messageId": "projects/my-app-prod/messages/1234"
}Schedule Notification
POST /api/notifications/schedule
Authorization: Bearer <owner-token>
Content-Type: application/json
{
"projectId": "YOUR_PROJECT_ID",
"userId": "USER_ID",
"title": "Reminder",
"body": "Don't forget!",
"data": {
"type": "reminder"
},
"sendAt": "2026-12-31T10:00:00.000Z"
}Response:
{
"success": true,
"scheduledId": "clx123abc"
}
userIdis optional. Omit for project-wide scheduled notifications.
Get History
GET /api/notifications/history?projectId=YOUR_PROJECT_ID&limit=50
Authorization: Bearer <owner-token>Response:
{
"success": true,
"notifications": [
{
"id": "clx123",
"userId": "user_abc",
"projectId": "proj_xyz",
"title": "Order Shipped",
"body": "Your order is on the way",
"data": { "type": "order" },
"status": "sent",
"successCount": 1,
"failureCount": 0,
"readAt": null,
"createdAt": "2026-09-22T10:30:00.000Z"
}
]
}Delete History
Delete a Single Notification
DELETE /api/notifications/history/:id
Authorization: Bearer <owner-token>Response:
{
"success": true,
"message": "Notification deleted"
}Clear All History for a Project
DELETE /api/notifications/history?projectId=YOUR_PROJECT_ID
Authorization: Bearer <owner-token>Response:
{
"success": true,
"deletedCount": 42,
"message": "Deleted 42 notification(s)"
}Get Preferences
GET /api/notifications/preferences
Authorization: Bearer <user-token>Response:
{
"success": true,
"preferences": {
"id": "clx456",
"userId": "user_abc",
"pushEnabled": true,
"emailEnabled": false,
"smsEnabled": false,
"marketingEnabled": true,
"securityEnabled": true,
"updatesEnabled": true,
"quietHoursEnabled": false,
"quietHoursStart": "22:00",
"quietHoursEnd": "08:00",
"timezone": "Asia/Kolkata",
"createdAt": "2026-09-22T10:00:00.000Z",
"updatedAt": "2026-09-22T10:00:00.000Z"
}
}Update Preferences
PUT /api/notifications/preferences
Authorization: Bearer <user-token>
Content-Type: application/json
{
"pushEnabled": true,
"emailEnabled": false,
"marketingEnabled": true,
"securityEnabled": true,
"quietHoursEnabled": true,
"quietHoursStart": "22:00",
"quietHoursEnd": "08:00",
"timezone": "Asia/Kolkata"
}Response:
{
"success": true,
"preferences": {
"pushEnabled": true,
"emailEnabled": false,
"marketingEnabled": true,
"securityEnabled": true,
"quietHoursEnabled": true,
"quietHoursStart": "22:00",
"quietHoursEnd": "08:00",
"timezone": "Asia/Kolkata"
}
}Preferences are stored but not automatically enforced. Your backend should respect them when choosing to send.
Error Codes
| Code | HTTP | Meaning | Fix |
|---|---|---|---|
FIREBASE_NOT_CONFIGURED | 400 | Service account not uploaded | Upload JSON in Notification Setup |
UNAUTHORIZED | 401 | Invalid or missing token | Re-authenticate |
NOT_PROJECT_OWNER | 403 | Token doesn't own this project | Use owner token |
PROJECT_NOT_FOUND | 404 | Invalid project ID | Check project ID |
INSUFFICIENT_QUOTA | 402 | Free plan database limit reached | Set up UPI auto-debit in Billing |
INVALID_SERVICE_ACCOUNT | 400 | Malformed JSON or missing fields | Re-download from Firebase Console |
DECRYPTION_FAILED | 500 | Encrypted credentials unreadable | Re-upload service account JSON |
Example error response:
{
"error": "Firebase not configured for this project. Please upload your service account credentials in Project Settings → Notifications.",
"code": "FIREBASE_NOT_CONFIGURED"
}Data Payload
The data field carries custom key-value pairs to the client. All values must be strings (FCM requirement — Moontraze auto-stringifies objects).
Size limit: 4 KB total (title + body + data + metadata).
Common patterns:
{
"type": "post",
"postId": "clx123",
"imageUrl": "https://cdn.example.com/img.jpg",
"deepLink": "myapp://post/clx123"
}{
"type": "chat",
"chatId": "chat_456",
"senderId": "user_abc"
}{
"type": "order",
"orderId": "order_789",
"status": "shipped"
}Client side (Flutter):
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
final data = message.data;
switch (data['type']) {
case 'post':
Navigator.pushNamed(context, '/post', arguments: data['postId']);
break;
case 'chat':
Navigator.pushNamed(context, '/chat', arguments: data['chatId']);
break;
}
});SDK Example (JavaScript)
const moon = new MoonSDK({
projectId: 'YOUR_PROJECT_ID',
apiKey: 'YOUR_API_KEY',
});
// Register token (from your client app)
await moon.notifications.registerToken(fcmToken, {
platform: 'android',
deviceId: 'unique-id',
});
// Unregister on logout
await moon.notifications.unregisterToken(fcmToken);
// Send to all users in project
await moon.notifications.sendToProject({
projectId: 'YOUR_PROJECT_ID',
title: 'Hello',
body: 'World',
});
// Send to specific user
await moon.notifications.sendToUser({
projectId: 'YOUR_PROJECT_ID',
userId: 'user_abc',
title: 'Order Shipped',
body: 'Your order is on the way',
data: { type: 'order', orderId: '1234' },
});
// Send to topic
await moon.notifications.sendToTopic({
projectId: 'YOUR_PROJECT_ID',
topic: 'premium_users',
title: 'Premium Feature',
body: 'Exclusive for premium users',
});
// Schedule for later
await moon.notifications.schedule({
projectId: 'YOUR_PROJECT_ID',
userId: 'user_abc',
title: 'Reminder',
body: "Don't forget!",
sendAt: new Date('2026-12-31T10:00:00Z'),
});
// History
const history = await moon.notifications.getHistory({
projectId: 'YOUR_PROJECT_ID',
limit: 50,
});
// Delete a notification
await moon.notifications.deleteHistory('NOTIFICATION_ID');
// Clear all history for a project
await moon.notifications.clearHistory({ projectId: 'YOUR_PROJECT_ID' });
// Preferences
const prefs = await moon.notifications.getPreferences();
await moon.notifications.updatePreferences({
pushEnabled: true,
quietHoursEnabled: true,
quietHoursStart: '22:00',
quietHoursEnd: '08:00',
});Rate Limits
No hard rate limits on Moontraze side. Firebase itself auto-scales to millions of messages per minute.
FCM payload limit: 4 KB per message. FCM multicast limit: 500 tokens per API call (Moontraze handles chunking).
Cost
Firebase Cloud Messaging is free and unlimited — no per-message charges, no monthly quota, no user limits.
You do NOT need a paid Firebase plan. The free Spark plan is sufficient.
Related Documentation
- Firebase Cloud Messaging docs: https://firebase.google.com/docs/cloud-messaging (opens in a new tab)
- FCM HTTP v1 API reference: https://firebase.google.com/docs/reference/fcm/rest (opens in a new tab)
Last updated: 2026-09-22