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/v1Auth: 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
| Principle | Detail |
|---|---|
| Slim shapes | Nested objects trimmed to IDs + display strings; full detail fetched on demand |
| No cursor pagination on small lists | Approval and campaign lists use simple limit/offset for predictable pull-to-refresh behaviour |
| SSE over polling | Real-time updates via SSE (not short-polling); the mobile SSE client reconnects automatically |
| Offline-tolerant | Read endpoints tolerate If-None-Match for ETag-based conditional fetches; no writes are cached client-side |
| Push notifications | Device 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.