CLI API (/cli/v1)
Status: [To Build] · Client:
apps/cli(lmREPL) · Audience: Internal Leadmetrics / DM team
The CLI API is a dedicated surface optimised for terminal workflows. Some endpoints are thin mappings to equivalent /dm/v1 or /admin/v1 endpoints; others are CLI-specific (bulk operations, REPL context, terminal-formatted streaming, script-friendly output).
Related: CLI App | CLI Commands | DM API | Admin API | Gateway
Base Prefix
/cli/v1Auth: API key (Authorization: ApiKey <key>) or JWT Bearer. API keys are the primary auth method for the CLI — they survive terminal sessions without a refresh flow.
Roles required: reviewer or super_admin
Tenant scoping: Optional. Pass ?tenantId= or X-Tenant-Id: <id> header to scope to a specific tenant. Omit to operate across all assigned tenants. Super admins can access any tenant.
Endpoint Map
Which endpoints are mappings vs CLI-native
| CLI command | Endpoint | Type | Maps to |
|---|---|---|---|
/status --all | GET /cli/v1/status | CLI-native | Aggregates /dm/v1/overview + health checks |
/tenants | GET /cli/v1/tenants | Thin mapping | → GET /admin/v1/tenants |
/queue | GET /cli/v1/queue | CLI-native | Aggregates /dm/v1/activities + /dm/v1/approvals |
/approve | POST /cli/v1/approvals/:id/resolve | Thin mapping | → POST /dm/v1/approvals/:id/resolve |
/reject | POST /cli/v1/approvals/:id/resolve | Thin mapping | → POST /dm/v1/approvals/:id/resolve |
/bulk-approve | POST /cli/v1/approvals/bulk-resolve | CLI-native | No equivalent — bulk only in CLI |
/run <agent> | POST /cli/v1/agents/:role/run | Thin mapping | → POST /dm/v1/agents/:role/run |
/agents | GET /cli/v1/agents/queues | Thin mapping | → GET /dm/v1/agents/queues |
/pause | POST /cli/v1/activities/:id/pause | Thin mapping | → POST /dm/v1/activities/:id/pause |
/resume | POST /cli/v1/activities/:id/resume | Thin mapping | → POST /dm/v1/activities/:id/resume |
/retry | POST /cli/v1/activities/:id/retry | Thin mapping | → POST /dm/v1/activities/:id/retry |
/activity | GET /cli/v1/activities/:id | Thin mapping | → GET /dm/v1/activities/:id |
/stream | GET /cli/v1/activities/:id/stream | CLI-native | SSE, terminal-optimised format |
/log | GET /cli/v1/activities/:id/log | Thin mapping | → GET /dm/v1/activities/:id/log |
/upload | POST /cli/v1/kb/upload | CLI-native | Multipart upload + progress SSE |
/kb | GET /cli/v1/kb | Thin mapping | → GET /dm/v1/kb |
/cost | GET /cli/v1/cost | Thin mapping | → GET /admin/v1/cost |
/impersonate | POST /cli/v1/impersonate | Thin mapping | → POST /admin/v1/tenants/:id/impersonate |
| NL query | POST /cli/v1/nl | CLI-native | No equivalent in other surfaces |
CLI-Native Endpoints
These endpoints exist only on /cli/v1 because they are tailored to terminal UX.
GET /status
Aggregated platform status — combines DM overview, infrastructure health, and cost rollup into a single terminal-optimised response. Avoids the need for multiple round trips on CLI startup.
Query params:
tenantId? — scope to specific tenant
all? — boolean; return all assigned tenants (default: true for super_admin)Response 200:
{
timestamp: string; // ISO 8601
tenants: Array<{
tenantId: string;
tenantName: string;
pendingApprovals: number;
highRiskApprovals: number;
runningAgents: number;
blockedActivities: number;
costMtd: number; // month-to-date cost in USD
creditUsed: number;
creditLimit: number;
}>;
totals: {
pendingApprovals: number;
runningAgents: number;
blockedActivities: number;
costMtd: number;
};
infrastructure: Array<{
service: string; // 'api' | 'postgres' | 'mongo' | 'redis' | 'qdrant' | 'ollama'
status: 'healthy' | 'degraded' | 'down';
latencyMs: number | null;
detail: string | null; // e.g. 'BullMQ: 12 active workers'
}>;
}GET /queue
Combined queue view — pending approvals + running activities + blocked activities for the current context. Designed to give a complete picture of “what needs attention right now” in a single request.
Query params:
tenantId? — scope to tenant
status? — pending | running | blocked | failed | all (default: pending+blocked)
type? — blog_post | social_post | backlink | report | ...
agent? — agent role slug
limit? — default 50, max 200
cursor? — pagination cursorResponse 200:
{
pending: Array<QueueItem>; // awaiting human approval
running: Array<QueueItem>; // agent currently executing
blocked: Array<QueueItem>; // blocked by dependency or error
failed: Array<QueueItem>; // failed, needs intervention
counts: {
pending: number;
running: number;
blocked: number;
failed: number;
};
pagination: PaginationMeta;
}
interface QueueItem {
activityId: string;
type: string;
title: string;
tenantId: string;
tenantName: string;
agentRole: string | null;
status: string;
riskLevel: 'low' | 'medium' | 'high' | null;
ageMinutes: number; // how long in current status
createdAt: string;
}POST /approvals/bulk-resolve
Approve or reject multiple items in a single request. No equivalent on other surfaces — bulk operations are a CLI/scripting concern.
Request body:
{
action: 'approve' | 'reject';
ids?: string[]; // explicit list of approval IDs
filter?: { // OR use a filter (not both)
type?: string;
riskLevel?: 'low' | 'medium' | 'high';
tenantId?: string;
ageMinutesGt?: number; // older than N minutes
};
note?: string; // optional note (appended to prompt on reject)
dryRun?: boolean; // preview without executing
}Response 200:
{
dryRun: boolean;
matched: number; // number of items matching the criteria
resolved: number; // number successfully actioned (0 if dryRun)
skipped: number; // already resolved or not accessible
items: Array<{
approvalId: string;
title: string;
tenantName: string;
result: 'resolved' | 'skipped' | 'error';
error?: string;
}>;
}GET /activities/:id/stream
SSE stream of activity output. Identical data to the DM Portal’s stream endpoint, but with an additional terminal metadata event that the CLI uses to render the activity header before content arrives.
Content-Type: text/event-stream
event: activity_meta
data: {"activityId":"01ARZ...","title":"Why Local SEO Matters","agentRole":"blog-writer","status":"running"}
event: text_delta
data: {"delta":"Local SEO is no longer optional..."}
event: text_delta
data: {"delta":" for businesses that rely on customers..."}
event: activity_completed
data: {"activityId":"01ARZ...","status":"awaiting_approval","wordCount":1240,"cost":0.018,"durationMs":220000}The activity_meta event is emitted first (even for completed activities being replayed) so the CLI can render the header box before content starts flowing.
For completed activities, the full output is replayed from MongoDB at a controlled pace (500 tokens/sec) to avoid flooding the terminal.
POST /kb/upload
Multipart file upload to a tenant’s knowledge base dataset. Emits SSE progress events so the CLI can render a live progress bar.
Request: multipart/form-data
file — the file (required)
datasetSlug — 'client-documents' | 'website-content' | 'competitor-research' (default: 'client-documents')
parseOnUpload — boolean, default true
parser — 'builtin' | 'docling', default 'builtin'
tenantId — required if not in X-Tenant-Id headerResponse: SSE stream (Content-Type: text/event-stream)
event: upload_started
data: {"fileName":"brand-brief.pdf","fileSize":1843200,"datasetId":"01ARZ..."}
event: upload_complete
data: {"ragFileId":"01BRZ...","status":"pending"}
event: ingestion_progress
data: {"ragFileId":"01BRZ...","stage":"parsing","progressPct":0}
event: ingestion_progress
data: {"ragFileId":"01BRZ...","stage":"embedding","progressPct":45,"chunksEmbedded":13,"chunksTotal":29}
event: ingestion_complete
data: {"ragFileId":"01BRZ...","chunksIndexed":29,"status":"indexed"}On error:
event: ingestion_error
data: {"ragFileId":"01BRZ...","error":"File too large — max 10MB","stage":"upload"}POST /nl
Natural language query endpoint. Accepts a plain-English string, interprets intent, and returns either:
- A data response (formatted for terminal output)
- An action proposal (what command to run, with a preview — the CLI presents this for confirmation)
- A clarification request (if intent is ambiguous)
Request body:
{
query: string; // the natural language input
context: {
tenantId?: string; // current session context
surface: 'cli';
history?: string[]; // last N commands for context
};
}Response 200:
{
type: 'data' | 'action' | 'clarification';
// type: 'data' — information response
data?: {
text: string; // plain text for terminal output
table?: TableData; // structured table the CLI renders
};
// type: 'action' — proposed command to execute
action?: {
command: string; // the slash command to run e.g. '/bulk-approve ...'
description: string; // human-readable description of what will happen
destructive: boolean; // whether this changes state
preview?: TableData; // what will be affected (for destructive ops)
};
// type: 'clarification' — need more info
clarification?: {
question: string;
options?: string[]; // suggested answers
};
}Thin Mappings
The following endpoints delegate directly to their /dm/v1 or /admin/v1 counterpart after gateway processing. They exist on /cli/v1 so the CLI only needs one base URL, and so CLI-specific gateway config (API key auth, higher rate limits) applies uniformly.
| CLI endpoint | Delegates to |
|---|---|
GET /cli/v1/tenants | GET /admin/v1/tenants |
POST /cli/v1/approvals/:id/resolve | POST /dm/v1/approvals/:id/resolve |
POST /cli/v1/agents/:role/run | POST /dm/v1/agents/:role/run |
GET /cli/v1/agents/queues | GET /dm/v1/agents/queues |
POST /cli/v1/activities/:id/pause | POST /dm/v1/activities/:id/pause |
POST /cli/v1/activities/:id/resume | POST /dm/v1/activities/:id/resume |
POST /cli/v1/activities/:id/retry | POST /dm/v1/activities/:id/retry |
GET /cli/v1/activities/:id | GET /dm/v1/activities/:id |
GET /cli/v1/activities/:id/log | GET /dm/v1/activities/:id/log |
GET /cli/v1/kb | GET /dm/v1/kb |
GET /cli/v1/cost | GET /admin/v1/cost |
POST /cli/v1/impersonate | POST /admin/v1/tenants/:id/impersonate |
Internally, these are implemented by calling the downstream handler function directly (not via HTTP) — same process, no network hop.
API Key Management
CLI users authenticate with long-lived API keys. Keys are managed via the Manage app (or the Admin API).
POST /admin/v1/api-keys — create key for a user
GET /admin/v1/api-keys — list keys
DELETE /admin/v1/api-keys/:id — revoke keyKey format: lm_live_<32-char random string> (prod) or lm_test_<...> (staging).
Keys are stored as a bcrypt hash in PostgreSQL. The plain key is shown once at creation and never again.
Rate Limits
The CLI surface has a higher rate limit than browser surfaces to support scripting use cases (bulk approve loops, morning status checks, etc.).
| Limit type | Value |
|---|---|
| Requests per minute | 1,200 |
| Burst allowance | +240 |
| Max concurrent requests | 15 |
| SSE connections | 5 |
| Bulk-resolve items per request | 500 |
| NL queries per minute | 20 (separate limit — LLM cost) |