Skip to Content
APICLI API (/cli/v1)

CLI API (/cli/v1)

Status: [To Build] · Client: apps/cli (lm REPL) · 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/v1

Auth: 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 commandEndpointTypeMaps to
/status --allGET /cli/v1/statusCLI-nativeAggregates /dm/v1/overview + health checks
/tenantsGET /cli/v1/tenantsThin mappingGET /admin/v1/tenants
/queueGET /cli/v1/queueCLI-nativeAggregates /dm/v1/activities + /dm/v1/approvals
/approvePOST /cli/v1/approvals/:id/resolveThin mappingPOST /dm/v1/approvals/:id/resolve
/rejectPOST /cli/v1/approvals/:id/resolveThin mappingPOST /dm/v1/approvals/:id/resolve
/bulk-approvePOST /cli/v1/approvals/bulk-resolveCLI-nativeNo equivalent — bulk only in CLI
/run <agent>POST /cli/v1/agents/:role/runThin mappingPOST /dm/v1/agents/:role/run
/agentsGET /cli/v1/agents/queuesThin mappingGET /dm/v1/agents/queues
/pausePOST /cli/v1/activities/:id/pauseThin mappingPOST /dm/v1/activities/:id/pause
/resumePOST /cli/v1/activities/:id/resumeThin mappingPOST /dm/v1/activities/:id/resume
/retryPOST /cli/v1/activities/:id/retryThin mappingPOST /dm/v1/activities/:id/retry
/activityGET /cli/v1/activities/:idThin mappingGET /dm/v1/activities/:id
/streamGET /cli/v1/activities/:id/streamCLI-nativeSSE, terminal-optimised format
/logGET /cli/v1/activities/:id/logThin mappingGET /dm/v1/activities/:id/log
/uploadPOST /cli/v1/kb/uploadCLI-nativeMultipart upload + progress SSE
/kbGET /cli/v1/kbThin mappingGET /dm/v1/kb
/costGET /cli/v1/costThin mappingGET /admin/v1/cost
/impersonatePOST /cli/v1/impersonateThin mappingPOST /admin/v1/tenants/:id/impersonate
NL queryPOST /cli/v1/nlCLI-nativeNo 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 cursor

Response 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 header

Response: 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 endpointDelegates to
GET /cli/v1/tenantsGET /admin/v1/tenants
POST /cli/v1/approvals/:id/resolvePOST /dm/v1/approvals/:id/resolve
POST /cli/v1/agents/:role/runPOST /dm/v1/agents/:role/run
GET /cli/v1/agents/queuesGET /dm/v1/agents/queues
POST /cli/v1/activities/:id/pausePOST /dm/v1/activities/:id/pause
POST /cli/v1/activities/:id/resumePOST /dm/v1/activities/:id/resume
POST /cli/v1/activities/:id/retryPOST /dm/v1/activities/:id/retry
GET /cli/v1/activities/:idGET /dm/v1/activities/:id
GET /cli/v1/activities/:id/logGET /dm/v1/activities/:id/log
GET /cli/v1/kbGET /dm/v1/kb
GET /cli/v1/costGET /admin/v1/cost
POST /cli/v1/impersonatePOST /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 key

Key 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 typeValue
Requests per minute1,200
Burst allowance+240
Max concurrent requests15
SSE connections5
Bulk-resolve items per request500
NL queries per minute20 (separate limit — LLM cost)

© 2026 Leadmetrics — Internal use only