Skip to Content
APIMobile API

Mobile API

Mobile-optimised API for the React Native dashboard app (iOS + Android). Most endpoints mirror the Dashboard API but return slim response shapes (fewer nested fields, smaller payloads) suited for mobile data budgets and screen layouts. Push notification registration is mobile-only.

Related: API Overview | Auth | Dashboard API | UI — Mobile App


Base Prefix

/mobile/v1

Auth: Bearer token with role: admin | member and appAccess: dashboard

The mobile app uses the same JWT as the web dashboard. Biometric unlock refreshes the access token without re-entering credentials — see Auth → Mobile: Biometric Auth.


Design principles

PrincipleDetail
Slim shapesNested objects trimmed to IDs + display strings; full detail fetched on demand
No cursor pagination on small listsApproval and campaign lists use simple limit/offset for predictable pull-to-refresh behaviour
SSE over pollingReal-time updates via SSE (not short-polling); the mobile SSE client reconnects automatically
Offline-tolerantRead endpoints tolerate If-None-Match for ETag-based conditional fetches; no writes are cached client-side
Push notificationsDevice registration, notification history, and mark-as-read are mobile-only

Push Notifications

POST /push/devices

Register a device for push notifications after a successful login.

Request:

{ deviceId: string; // stable UUID generated and persisted by the app pushToken: string; // FCM (Android) or APNs (iOS) token platform: 'ios' | 'android'; appVersion: string; }

Response 201:

{ deviceId: string; registered: true; }

Push tokens rotate (especially on iOS). The app should call this endpoint on every login and whenever onTokenRefresh fires.


PATCH /push/devices/:deviceId

Update a device’s push token (called when the OS issues a new token).

Request:

{ pushToken: string; }

Response 200:

{ deviceId: string; updated: true; }

DELETE /push/devices/:deviceId

Deregister a device. Called on logout or when the user disables notifications.

Response 204: No content


GET /notifications

Notification history for the authenticated user (last 90 days, newest first).

Query params:

{ filter?: { read?: boolean }; limit?: number; // default 50 offset?: number; }

Response 200:

{ data: Array<{ id: string; type: 'approval_created' | 'approval_expiring' | 'activity_completed' | 'activity_failed' | 'budget_alert' | 'campaign_update'; title: string; body: string; read: boolean; deepLink: string; // e.g. 'leadmetrics://approvals/01ARZ...' createdOn: string; }>; unreadCount: number; }

PATCH /notifications/read

Mark notifications as read (bulk).

Request:

{ ids?: string[]; // specific IDs; omit to mark all as read markAll?: boolean; }

Response 200:

{ marked: number; }

Campaigns

GET /campaigns

Slim campaign list for the home screen.

Query params: filter.status, limit (default 20), offset

Response 200:

{ data: Array<{ id: string; name: string; status: string; pendingApprovals: number; activitiesInProgress: number; spentUsd: number; budgetCapUsd: number | null; updatedOn: string; }>; total: number; }

GET /campaigns/:campaignId

Campaign detail — trimmed for mobile layout.

Response 200:

{ id: string; name: string; status: string; brief: string; spentUsd: number; budgetCapUsd: number | null; pendingApprovals: number; deliverables: Array<{ id: string; type: string; quantity: number; completed: number; // current period inProgress: number; status: string; }>; recentActivities: Array<{ id: string; title: string; status: string; agentRole: string; updatedOn: string; }>; }

Approvals

GET /approvals

Approval queue — the primary mobile use case. Sorted high-risk first by default.

Query params:

{ filter?: { status?: 'pending' | 'approved' | 'rejected' | 'expired'; riskLevel?: 'low' | 'medium' | 'high'; }; limit?: number; // default 25 offset?: number; }

Response 200:

{ data: Array<{ id: string; title: string; type: string; riskLevel: string; description?: string; options?: string[]; expiresAt: string; expiresInH: number; // hours until expiry (computed, for countdown display) createdOn: string; linkedActivitiesCount: number; }>; total: number; pendingCount: number; highRiskCount: number; }

GET /approvals/:approvalId

Approval detail with output preview for mobile review.

Response 200:

{ id: string; title: string; type: string; riskLevel: string; description?: string; options?: string[]; status: string; expiresAt: string; expiresInH: number; linkedActivities: Array<{ id: string; title: string; outputPreview?: string; // first 800 chars; full text at /activities/:id/output outputRef?: string; }>; createdBy: { name: string; type: 'agent' | 'human' }; createdOn: string; }

POST /approvals/:approvalId/resolve

Approve or reject. Same request shape as the Dashboard API.

Request:

{ resolution: 'approved' | 'rejected' | 'revision_requested'; selectedOption?: string; note?: string; }

Response 200:

{ approvalId: string; resolution: string; reEnqueuedActivities: number; }

Activities

GET /activities

Activity list — slim, for the mobile activity feed.

Query params: filter.status, filter.campaignId, limit (default 20), offset

Response 200:

{ data: Array<{ id: string; title: string; status: string; agentRole: string; campaign: { id: string; name: string }; updatedOn: string; }>; total: number; }

GET /activities/:activityId

Activity detail — mobile layout. Includes output preview; full output at /activities/:activityId/output.

Response 200:

{ id: string; title: string; type: string; status: string; agentRole: string; campaign: { id: string; name: string }; outputPreview?: string; outputRef?: string; approval?: { id: string; type: string; status: string }; lastRunAt?: string; costUsd: number; createdOn: string; }

GET /activities/:activityId/output

Full output text for a completed activity. Same as the dashboard endpoint — no mobile-specific trimming (the full article is needed for approval review).

Response 200:

{ activityId: string; text: string; format: 'markdown' | 'html' | 'plain'; }

GET /activities/:activityId/stream

SSE: live agent output. Same protocol as the dashboard endpoint. The React Native eventsource client handles reconnection.


POST /activities/:activityId/comments

Add a comment. Same as the dashboard endpoint.


Channels

GET /channels

Channel health overview for the mobile channels screen.

Response 200:

{ data: Array<{ id: string; name: string; type: string; status: 'connected' | 'expired' | 'error' | 'disconnected'; healthScore?: number; accountName?: string; }>; }

POST /channels/:platform/connect

Initiate OAuth. Returns authUrl — mobile opens this in the system browser (not an in-app WebView).

Same request/response as dashboard channel connect endpoint.


Analytics

GET /analytics/spend

Spend summary for the mobile cost widget on the home screen.

Response 200:

{ totalUsd: number; budgetCapUsd?: number; budgetUsedPct?: number; topAgents: Array<{ agentRole: string; costUsd: number; }>; last7Days: Array<{ date: string; // YYYY-MM-DD costUsd: number; }>; }

Events (SSE)

GET /events/stream

Mobile SSE stream — same events as the dashboard stream but with the deepLink field added to each event payload so the app can navigate directly on notification tap.

Protocol: text/event-stream

Events (example):

event: approval_created data: { "approvalId":"01ARZ...", "title":"Blog Post Review", "riskLevel":"high", "deepLink":"leadmetrics://approvals/01ARZ..." } event: activity_completed data: { "activityId":"01ARZ...", "title":"Writing blog post", "deepLink":"leadmetrics://activities/01ARZ..." }

Home Screen Aggregate

GET /home

Single endpoint that aggregates all data needed for the D1 Home screen in one request, reducing round-trips on app launch.

Response 200:

{ pendingApprovals: { count: number; highRisk: number; items: ApprovalSummary[]; // top 3, sorted high risk first }; recentActivities: { inProgress: number; items: ActivitySummary[]; // most recent 5 }; spend: { thisMonthUsd: number; budgetCapUsd?: number; budgetUsedPct?: number; }; campaigns: { active: number; atRisk: number; }; }

This endpoint is served from a short-lived (60 s) cache per tenant to reduce database load on app launch.

© 2026 Leadmetrics — Internal use only