📱 SOL9X Threat Monitoring & Logging — Mobile App Integration Guide

This document is the official API and integration reference for the SOL9X Mobile App engineering team (iOS, Android, Flutter, React Native). It details how to authenticate members, manage Firebase Cloud Messaging (FCM) device tokens, receive real-time push notifications for critical threats, and build the incident triage dashboard.


🌐 1. Server Endpoints & Base Configuration

Environment Base URL
Production https://api.logger.sol9x.in/api/v1
Local Development http://localhost:4000/api/v1

Global Headers

Every authenticated endpoint requires standard JSON headers and the Bearer JWT received upon login:

Content-Type: application/json
Authorization: Bearer <JWT_ACCESS_TOKEN>

🔐 2. Authentication & FCM Token Lifecycle

The backend ties device FCM registration tokens to user accounts for targeted push delivery to assigned project members. Follow this 3-step lifecycle:

[ Mobile App Launch ] ──► [ Request FCM Token ]
                                    │
                                    ▼
       ┌─────────────────── [ User Logs In ] ──────────────────┐
       │ (Sends email, password, and device fcmToken)         │
       ▼                                                      ▼
[ Backend Stores Token ]                              [ Mobile Stores JWT ]
       │                                                      │
       ▼                                                      ▼
[ FCM onTokenRefresh Triggered? ] ──► [ PUT /auth/fcm-token ] │
                                                              ▼
                                                   [ User Logs Out ]
                                                              │
                                                              ▼
                                                   [ POST /auth/logout ]
                                             (Backend clears device token)

A. Login & Register FCM Token

Authenticate the user with email/password and automatically register the current device's FCM token.

{
  "email": "engineer@company.com",
  "password": "mySecurePassword123",
  "fcmToken": "c1a2b3c4d5e6f7g8h9i0_sample_fcm_registration_token"
}

Note: fcmToken is optional. If the user denies notification permissions, omit fcmToken. If granted, always send it.

{
  "success": true,
  "message": "Login successful.",
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "user": {
      "id": "cmvetybcu0002itfnmirhvn2d",
      "name": "Jane Developer",
      "email": "engineer@company.com",
      "role": "MEMBER",
      "assignedProjects": [
        {
          "id": "proj_clv123456",
          "name": "Payment Service",
          "slug": "payment-service"
        },
        {
          "id": "proj_clv987654",
          "name": "Auth Gateway",
          "slug": "auth-gateway"
        }
      ]
    }
  }
}

If role is "ADMIN", assignedProjects will be the string "ALL".


B. Silent FCM Token Rotation

When the Firebase SDK rotates the device registration token (e.g., app reinstall, OS update, or periodic refresh), call this endpoint silently in the background.

{
  "fcmToken": "new_refreshed_fcm_token_xxxxxxxxxxxxxxxx"
}
{
  "success": true,
  "message": "FCM token updated successfully."
}

C. Logout (Unregister Device Token)

Always call this endpoint when the user taps Log Out. It removes the device's fcmToken from the database so the device stops receiving notifications for that account.

Authorization: Bearer <JWT_ACCESS_TOKEN>
{
  "success": true,
  "message": "Logged out successfully."
}

D. Get Current User Profile & Scopes

Used on app launch to validate stored JWT sessions and retrieve assigned projects.

{
  "success": true,
  "data": {
    "id": "cmvetybcu0002itfnmirhvn2d",
    "name": "Jane Developer",
    "email": "engineer@company.com",
    "role": "MEMBER",
    "assignedProjects": [
      {
        "id": "proj_clv123456",
        "name": "Payment Service",
        "slug": "payment-service"
      }
    ]
  }
}

🔔 3. FCM Push Notification Payload & Deep-Linking

When a critical alert triggers (e.g., server crash, cascading 5xx surge, or DB connection outage), the backend pushes an FCM multicast notification to all active devices of members assigned to the affected project.

Notification Channels (Android)

Configure these two notification channels in your Android Application class or Flutter/React Native setup:

  1. critical_alerts — Importance: MAX, Sound: Default (or High-priority alarm)
  2. threat_alerts — Importance: HIGH, Sound: Default

FCM Payload Structure

{
  "notification": {
    "title": "🚨 [CRITICAL] Infrastructure / Database Failures",
    "body": "payment-api (production): PrismaClientKnownRequestError: Can't reach database server"
  },
  "data": {
    "incidentId": "cmveu174i0003ithyy2twy9lp",
    "projectId": "proj_clv123456",
    "ruleId": "rule_cmi789012",
    "severity": "CRITICAL",
    "service": "payment-api",
    "environment": "production",
    "rootLogId": "log_clv654321",
    "click_action": "FLUTTER_NOTIFICATION_CLICK"
  }
}

Deep-Linking Logic

When the user taps on the push notification:

  1. Extract data["incidentId"].
  2. Check if the app is currently authenticated.
  3. Navigate immediately to the Incident Detail Screen: Navigator.pushNamed(context, '/threats/detail', arguments: incidentId);

🚨 4. Threat Incident Feed & Triage APIs


A. List Threat Incidents

Fetches threat incidents. The response is automatically scoped to the projects the logged-in user is assigned to.

Parameter Type Required Default Description
projectId string No all Filter by specific project ID
status string No all OPEN, ACKNOWLEDGED, or RESOLVED
severity string No all CRITICAL or INTERMEDIATE
page integer No 1 Page number (1-based)
limit integer No 20 Items per page (max 100)
curl -X GET "https://api.logger.sol9x.in/api/v1/threats?status=OPEN&severity=CRITICAL&page=1&limit=20" \
  -H "Authorization: Bearer <JWT_ACCESS_TOKEN>"
{
  "success": true,
  "data": {
    "threats": [
      {
        "id": "cmveu174i0003ithyy2twy9lp",
        "projectId": "proj_clv123456",
        "ruleId": "rule_cmi789012",
        "severity": "CRITICAL",
        "status": "OPEN",
        "fingerprint": "a4b2c1d9e8f7...",
        "title": "5xx Cascading Error Surge",
        "service": "order-service",
        "environment": "production",
        "errorType": "InternalServerError",
        "message": "Transaction failed: DB lock timeout exceeded",
        "rootLogId": "log_clv654321",
        "occurrenceCount": 14,
        "lastSeenAt": "2026-10-19T05:47:18.000Z",
        "acknowledgedAt": null,
        "acknowledgedById": null,
        "resolvedAt": null,
        "resolvedById": null,
        "createdAt": "2026-10-19T05:30:00.000Z",
        "project": {
          "id": "proj_clv123456",
          "name": "Order Service",
          "slug": "order-service"
        },
        "rule": {
          "id": "rule_cmi789012",
          "name": "5xx Cascading Error Surge",
          "severity": "CRITICAL"
        }
      }
    ],
    "pagination": {
      "page": 1,
      "limit": 20,
      "total": 1,
      "totalPages": 1
    }
  }
}

B. Get Threat Incident Details

Fetches complete details for a single incident, including the triggering alert rule and the full root-cause rootLog (stack trace, request/trace ID, HTTP status, and duration).

{
  "success": true,
  "data": {
    "id": "cmveu174i0003ithyy2twy9lp",
    "projectId": "proj_clv123456",
    "ruleId": "rule_cmi789012",
    "severity": "CRITICAL",
    "status": "OPEN",
    "fingerprint": "a4b2c1d9e8f7...",
    "title": "5xx Cascading Error Surge",
    "service": "order-service",
    "environment": "production",
    "errorType": "PaymentDeadlockException",
    "message": "Critical error: PaymentDeadlock detected on checkout table",
    "rootLogId": "log_clv654321",
    "occurrenceCount": 14,
    "lastSeenAt": "2026-10-19T05:47:18.000Z",
    "acknowledgedAt": null,
    "acknowledgedById": null,
    "resolvedAt": null,
    "resolvedById": null,
    "createdAt": "2026-10-19T05:30:00.000Z",
    "project": {
      "id": "proj_clv123456",
      "name": "Order Service",
      "slug": "order-service"
    },
    "rule": {
      "id": "rule_cmi789012",
      "name": "5xx Cascading Error Surge",
      "severity": "CRITICAL",
      "thresholdCount": 10,
      "windowSeconds": 60,
      "cooldownMinutes": 15
    },
    "rootLog": {
      "id": "log_clv654321",
      "projectId": "proj_clv123456",
      "environment": "production",
      "service": "order-service",
      "level": "ERROR",
      "message": "Critical error: PaymentDeadlock detected on checkout table",
      "errorType": "PaymentDeadlockException",
      "stackTrace": "Error: PaymentDeadlock\n    at checkout (/app/dist/order.js:42:15)\n    at processTicksAndRejections...",
      "httpMethod": "POST",
      "endpoint": "/api/v1/orders/checkout",
      "statusCode": 500,
      "duration": 1200.5,
      "requestId": "req_a1b2c3d4",
      "traceId": "trace_9988776655",
      "userId": "usr_test_1",
      "metadata": {
        "cartId": "cart_908123",
        "attempt": 3
      },
      "createdAt": "2026-10-19T05:30:00.000Z"
    }
  }
}

C. Update Incident Status (Acknowledge / Resolve)

Allows on-call mobile responders to acknowledge an active alert or resolve it once fixed.

{
  "status": "ACKNOWLEDGED"
}
{
  "status": "RESOLVED"
}
{
  "success": true,
  "message": "Threat incident status updated successfully.",
  "data": {
    "id": "cmveu174i0003ithyy2twy9lp",
    "status": "ACKNOWLEDGED",
    "acknowledgedAt": "2026-10-19T05:52:00.000Z",
    "acknowledgedById": "cmvetybcu0002itfnmirhvn2d",
    "resolvedAt": null,
    "resolvedById": null,
    "occurrenceCount": 14
  }
}

📜 5. Log Explorer APIs (Mobile Inspection)

Mobile members can also inspect surrounding logs when diagnosing an incident.

A. Query Logs

Parameter Type Example Description
projectId string proj_clv123456 Scoped to project
levels string ERROR,FATAL Comma-separated log levels
service string order-service Filter by microservice name
environment string production production, staging, etc.
search string deadlock Free-text search on message & errorType
page integer 1 Page number
limit integer 25 Default 20, max 100
{
  "data": [
    {
      "id": "log_clv654321",
      "projectId": "proj_clv123456",
      "service": "order-service",
      "level": "ERROR",
      "message": "Transaction failed: deadlock detected",
      "statusCode": 500,
      "duration": 450.2,
      "timestamp": "2026-10-19T05:46:10.000Z"
    }
  ],
  "meta": {
    "total": 1,
    "page": 1,
    "limit": 25,
    "totalPages": 1
  }
}

B. View Single Log Details

{
  "success": true,
  "data": {
    "id": "log_clv654321",
    "projectId": "proj_clv123456",
    "service": "order-service",
    "level": "ERROR",
    "message": "Transaction failed: deadlock detected",
    "errorType": "PaymentDeadlockException",
    "stackTrace": "...",
    "metadata": {}
  }
}

🛠️ 6. Client Code Samples

Flutter (Dart) Example

1. Setup Push Notifications & Token Rotation

import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

class NotificationService {
  final FirebaseMessaging _fcm = FirebaseMessaging.instance;
  String? _jwtToken;

  void setAuthToken(String token) {
    _jwtToken = token;
  }

  Future<void> initialize(Function(String incidentId) onOpenIncident) async {
    // 1. Request OS notification permission
    NotificationSettings settings = await _fcm.requestPermission(
      alert: true,
      badge: true,
      sound: true,
    );

    if (settings.authorizationStatus != AuthorizationStatus.authorized) {
      return;
    }

    // 2. Handle token refresh silently
    _fcm.onTokenRefresh.listen((newToken) async {
      if (_jwtToken != null) {
        await http.put(
          Uri.parse('https://api.logger.sol9x.in/api/v1/auth/fcm-token'),
          headers: {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer $_jwtToken',
          },
          body: jsonEncode({'fcmToken': newToken}),
        );
      }
    });

    // 3. Handle Notification Tap when app is running in background
    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      final incidentId = message.data['incidentId'];
      if (incidentId != null) {
        onOpenIncident(incidentId);
      }
    });

    // 4. Handle Notification Tap when app was completely terminated (Cold Start)
    RemoteMessage? initialMessage = await _fcm.getInitialMessage();
    if (initialMessage != null) {
      final incidentId = initialMessage.data['incidentId'];
      if (incidentId != null) {
        onOpenIncident(incidentId);
      }
    }
  }

  // Retrieve current token for Login API
  Future<String?> getDeviceToken() async {
    return await _fcm.getToken();
  }
}

2. Incident Triage Action (Acknowledge / Resolve)

Future<void> updateIncidentStatus(String incidentId, String status, String jwt) async {
  final response = await http.patch(
    Uri.parse('https://api.logger.sol9x.in/api/v1/threats/$incidentId/status'),
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer $jwt',
    },
    body: jsonEncode({'status': status}), // "ACKNOWLEDGED" or "RESOLVED"
  );

  if (response.statusCode != 200) {
    throw Exception('Failed to update incident: ${response.body}');
  }
}

React Native / TypeScript Example

import messaging from '@react-native-firebase/messaging';
import axios from 'axios';

const API_BASE = 'https://api.logger.sol9x.in/api/v1';

export async function loginMobile(email: string, password: string) {
  // 1. Request notification permissions
  const authStatus = await messaging().requestPermission();
  const enabled =
    authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
    authStatus === messaging.AuthorizationStatus.PROVISIONAL;

  let fcmToken: string | undefined;
  if (enabled) {
    fcmToken = await messaging().getToken();
  }

  // 2. Authenticate
  const response = await axios.post(`${API_BASE}/auth/login`, {
    email,
    password,
    fcmToken,
  });

  const { token, user } = response.data.data;

  // 3. Listen for token refreshes
  messaging().onTokenRefresh(async (newToken) => {
    await axios.put(
      `${API_BASE}/auth/fcm-token`,
      { fcmToken: newToken },
      { headers: { Authorization: `Bearer ${token}` } }
    );
  });

  return { token, user };
}

export function setupNotificationHandlers(navigation: any) {
  // Foreground notification handler
  messaging().onMessage(async (remoteMessage) => {
    console.log('Received foreground threat alert:', remoteMessage.notification);
  });

  // Background notification tap
  messaging().onNotificationOpenedApp((remoteMessage) => {
    const incidentId = remoteMessage.data?.incidentId;
    if (incidentId) {
      navigation.navigate('ThreatDetailScreen', { incidentId });
    }
  });

  // Cold start notification tap
  messaging()
    .getInitialNotification()
    .then((remoteMessage) => {
      const incidentId = remoteMessage?.data?.incidentId;
      if (incidentId) {
        navigation.navigate('ThreatDetailScreen', { incidentId });
      }
    });
}

⚠️ 7. Standard Error Responses

All endpoints adhere to standard HTTP status codes and provide a consistent JSON error schema:

{
  "success": false,
  "error": "ForbiddenError",
  "message": "You do not have access to logs of this project."
}
HTTP Status Error Type Cause / Solution
400 ValidationError Malformed body or missing required field (check message)
401 UnauthorizedError Missing, expired, or invalid Bearer JWT token
403 ForbiddenError Attempted to access an incident or project not assigned to this user
404 NotFoundError Incident or Log ID does not exist
500 InternalServerError Server-side unexpected exception

📞 Support & Contacts