Documentation
API Reference
Push Notifications

Push Notifications API

Send push notifications via your own Firebase project (BYOF). 100% free and unlimited.


Base URL

https://api.moontraze.com

Permission Model

RoleConfigure FirebaseSendReceive
OwnerYesYesYes
Admin (with permission)NoYesYes
UserNoNoYes

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 login
  • X-Project-Id — Your Moontraze project ID
  • X-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
}

projectId is required in the body (also accepted via X-Project-Id header).


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": []
}

failedTokens contains 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
}

projectId required in body for send-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"
}

userId is 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

CodeHTTPMeaningFix
FIREBASE_NOT_CONFIGURED400Service account not uploadedUpload JSON in Notification Setup
UNAUTHORIZED401Invalid or missing tokenRe-authenticate
NOT_PROJECT_OWNER403Token doesn't own this projectUse owner token
PROJECT_NOT_FOUND404Invalid project IDCheck project ID
INSUFFICIENT_QUOTA402Free plan database limit reachedSet up UPI auto-debit in Billing
INVALID_SERVICE_ACCOUNT400Malformed JSON or missing fieldsRe-download from Firebase Console
DECRYPTION_FAILED500Encrypted credentials unreadableRe-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


Last updated: 2026-09-22