Skip to Content
APIDashboard API

Dashboard API

Client-facing API for tenant marketing teams. All endpoints are scoped to the authenticated user’s tenant — callers never pass tenantId.

Related: API Overview | Auth | Workflow Model | Governance


Base Prefix

/dashboard/v1

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


Campaigns

GET /campaigns

List campaigns for the tenant.

Query params:

{ filter?: { status?: 'draft' | 'active' | 'paused' | 'completed' | 'archived'; }; limit?: number; // default 25 cursor?: string; order?: 'asc' | 'desc'; // default desc }

Response 200:

{ data: Array<{ id: string; name: string; status: string; budgetCapUsd: number | null; spentUsd: number; deliverableCount: number; activityCount: { total: number; inProgress: number; pending: number; done: number }; pendingApprovals: number; createdBy: string; createdOn: string; startDate: string | null; endDate: string | null; }>; pagination: PaginatedResponse; }

POST /campaigns

Submit a new campaign brief. Triggers the Activity Planner to plan the activity pipeline.

Auth note: admin only

Request:

{ name: string; brief: string; // campaign goals and context deliverables: Array<{ type: 'blog_post' | 'social_post' | 'gbp_post' | 'ad_copy' | 'email_newsletter' | 'backlink' | 'website_audit' | 'performance_report'; quantity: number; // per period platformIds?: string[]; // channel ref_ids to target templateId?: string; // activity template ref_id (optional override) }>; budgetCapUsd?: number; startDate?: string; // ISO 8601 date endDate?: string; attachments?: string[]; // MongoDB refIds for uploaded brand/brief files }

Response 201:

{ id: string; name: string; status: 'draft'; }

Side effects: Activity Planner activity is enqueued immediately. Strategy planning typically completes within 2–5 minutes.


GET /campaigns/:campaignId

Campaign detail including deliverables, current period status, and cost breakdown.

Response 200:

{ id: string; name: string; status: string; brief: string; budgetCapUsd: number | null; spentUsd: number; startDate: string | null; endDate: string | null; createdBy: { id: string; name: string }; createdOn: string; deliverables: Array<{ id: string; type: string; quantity: number; platforms: Array<{ id: string; name: string; type: string }>; currentPeriod?: { periodStart: string; periodEnd: string; target: number; completed: number; inProgress: number; status: 'on_track' | 'at_risk' | 'missed'; }; }>; recentActivities: Array<ActivitySummary>; pendingApprovals: number; costByAgent: Array<{ agentRole: string; costUsd: number }>; }

PATCH /campaigns/:campaignId

Update campaign metadata. Cannot change deliverables on an active campaign.

Auth note: admin only

Request:

{ name?: string; brief?: string; budgetCapUsd?: number | null; status?: 'paused' | 'active'; // pause or resume }

Response 200: Updated campaign summary


DELETE /campaigns/:campaignId

Archive a campaign. All in-progress activities are paused; no new activities are created. Archived campaigns are excluded from the default list view.

Auth note: admin only

Response 204: No content


Deliverables

GET /campaigns/:campaignId/deliverables

List deliverables for a campaign with per-period progress.

Response 200:

{ data: Array<{ id: string; type: string; quantity: number; periods: Array<{ id: string; periodStart: string; periodEnd: string; target: number; completed: number; inProgress: number; status: string; }>; }>; }

GET /deliverables/:deliverableId/activities

List all activities for a specific deliverable.

Query params: filter.status, limit, cursor

Response 200: Paginated list of ActivitySummary


Activities

GET /activities

List activities for the tenant with filtering.

Query params:

{ filter?: { status?: 'created' | 'assigned' | 'in_progress' | 'awaiting_approval' | 'awaiting_subtask' | 'awaiting_review' | 'blocked' | 'done' | 'reassigned' | 'failed'; agentRole?: string; campaignId?: string; deliverableId?: string; wakeReason?: string; }; limit?: number; cursor?: string; }

Response 200: Paginated ActivitySummary[]

interface ActivitySummary { id: string; title: string; type: string; status: string; agentRole: string | null; assigneeName: string; campaignId: string; campaignName: string; deliverableId?: string; approvalId?: string; wakeReason?: string; startedAt?: string; completedAt?: string; costUsd: number; createdOn: string; }

GET /activities/:activityId

Full activity detail including run history and output.

Response 200:

{ id: string; title: string; type: string; status: string; agentRole: string | null; assignee: { id: string; name: string; type: 'agent' | 'human' }; campaign: { id: string; name: string }; deliverable?: { id: string; type: string }; approval?: { id: string; type: string; status: string }; inputPrompt: string; outputRef?: string; // MongoDB refId for full output text outputPreview?: string; // first 500 chars of output comments: ActivityComment[]; runs: Array<{ id: string; status: 'completed' | 'failed' | 'timeout'; model: string; inputTokens: number; outputTokens: number; costUsd: number; durationMs: number; startedAt: string; completedAt?: string; error?: string; }>; subtasks: ActivitySummary[]; createdOn: string; updatedOn: string; }

GET /activities/:activityId/output

Fetch the full output text for a completed activity (stored in MongoDB).

Response 200:

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

GET /activities/:activityId/stream

SSE: stream live agent output text for an in-progress activity.

Protocol: text/event-stream

Events:

event: text_delta data: {"delta":"Based on the keyword research...","activityId":"01ARZ..."} event: tool_call data: {"name":"semrush_keyword_research","input":{"keyword":"digital marketing"},"activityId":"01ARZ..."} event: tool_result data: {"name":"semrush_keyword_research","output":{"volume":8100},"activityId":"01ARZ..."} event: completed data: {"activityId":"01ARZ...","status":"done"} event: error data: {"activityId":"01ARZ...","error":"timeout"}

The SSE connection closes automatically on completed or error.


POST /activities/:activityId/retry

Retry a failed activity. Creates a new activity run and re-enqueues the job.

Auth note: admin only

Request: Empty body

Response 202:

{ activityId: string; runId: string; }

Errors:

  • 422 UNPROCESSABLE — activity is not in failed status
  • 422 UNPROCESSABLE — activity belongs to a paused or archived campaign

POST /activities/:activityId/comments

Add a comment or @ mention to an activity.

Request:

{ text: string; // supports @agent:role, @human:userId mention syntax type?: 'comment' | 'intervention_note'; // default 'comment' }

Response 201:

{ id: string; text: string; type: string; author: { id: string; name: string; type: 'human' | 'agent' }; mentions: Array<{ type: 'agent' | 'human'; id: string; name: string }>; createdOn: string; }

GET /activities/:activityId/comments

List all comments on an activity, ordered oldest first.

Response 200:

{ data: ActivityComment[]; }

Approvals

GET /approvals

List pending approvals for the tenant.

Query params:

{ filter?: { status?: 'pending' | 'approved' | 'rejected' | 'expired'; type?: 'content_review' | 'content_direction' | 'brand_direction' | 'strategy_change' | 'budget_authorization' | 'channel_action'; riskLevel?: 'low' | 'medium' | 'high'; }; limit?: number; cursor?: string; }

Response 200: Paginated list:

Array<{ id: string; title: string; type: string; riskLevel: string; status: string; description?: string; options?: string[]; linkedActivities: Array<{ id: string; title: string; status: string }>; createdBy: { id: string; name: string; type: 'agent' | 'human' }; createdOn: string; expiresAt: string; resolvedAt?: string; resolvedBy?: { id: string; name: string }; resolution?: string; resolutionNote?: string; }>

GET /approvals/:approvalId

Approval detail including linked activities and full output content for review.

Response 200: Full approval record (same shape as list item) plus:

{ // ...same as list item... linkedActivities: Array<{ id: string; title: string; status: string; outputRef?: string; outputPreview?: string; }>; }

POST /approvals/:approvalId/resolve

Approve, reject, or request revision for a pending approval.

Auth note: admin role; member cannot resolve approvals

Request:

{ resolution: 'approved' | 'rejected' | 'revision_requested'; selectedOption?: string; // for approvals with options[] — required if options are defined note?: string; // required when resolution is 'rejected' or 'revision_requested' }

Response 200:

{ approvalId: string; resolution: string; reEnqueuedActivities: number; // count of activities re-enqueued (for approved) }

Side effects:

  • approved — all linked activities are re-enqueued with wakeReason: 'review_approved'
  • rejected — all linked activities are re-enqueued with wakeReason: 'review_feedback' and the note
  • revision_requested — same as rejected but with reviewerFeedback context

Errors:

  • 409 CONFLICT — approval is not in pending status
  • 422 UNPROCESSABLEselectedOption is required but missing
  • 400 VALIDATION_ERRORnote is required for reject/revision but missing

GET /approvals/stats

Summary counts for the approval queue — used by the D1 Home screen dashboard widget.

Response 200:

{ pending: number; highRisk: number; // pending + riskLevel:high expiringIn24h: number; // pending + expiresAt within 24h resolvedThisWeek: number; }

Channels

GET /channels

List connected channels for the tenant.

Response 200:

{ data: Array<{ id: string; name: string; type: 'linkedin' | 'facebook' | 'instagram' | 'tiktok' | 'wordpress' | 'google_business_profile' | 'google_ads' | 'meta_ads' | 'mailchimp' | 'klaviyo' | 'google_analytics' | 'google_search_console'; status: 'connected' | 'expired' | 'error' | 'disconnected'; accountName?: string; healthScore?: number; // 0–100, most recent monthly score lastCheckedAt?: string; connectedAt: string; connectedBy: string; // user name }>; }

POST /channels/:platform/connect

Initiate OAuth flow for a channel. Returns a redirect URL; the client should redirect the user to it. The OAuth callback lands on a Leadmetrics endpoint that stores the tokens and redirects back to the dashboard.

Path params: platform — one of the type values above

Request:

{ redirectUrl: string; // where to redirect after OAuth completes (must be allow-listed) }

Response 200:

{ authUrl: string; // redirect user here state: string; // CSRF state; validate on return }

DELETE /channels/:channelId

Disconnect a channel. Revokes stored tokens and marks the channel as disconnected.

Auth note: admin only

Response 204: No content


GET /channels/:channelId/health

Current health score and breakdown for a connected channel.

Response 200:

{ channelId: string; healthScore: number; // 0–100 breakdown: { completeness: number; // profile completeness activityScore: number; // posting frequency vs target engagement: number; // avg engagement rate }; lastPublishedAt?: string; computedAt: string; }

Leads

GET /leads

List leads for the tenant.

Query params:

{ filter?: { status?: 'new' | 'contacted' | 'qualified' | 'unqualified' | 'converted'; source?: 'form' | 'google_ads' | 'meta_ads' | 'manual' | 'import'; }; search?: string; // name or email fuzzy search limit?: number; cursor?: string; }

Response 200: Paginated list:

Array<{ id: string; firstName: string; lastName: string; email?: string; phone?: string; company?: string; status: string; source: string; sourceRef?: string; // e.g. Google Ads campaign name score?: number; // 0–100 qualification score assignedTo?: { id: string; name: string }; tags: string[]; createdOn: string; }>

POST /leads

Create a lead manually.

Request:

{ firstName: string; lastName: string; email?: string; phone?: string; company?: string; source?: string; // default 'manual' notes?: string; tags?: string[]; }

Response 201: Created lead


GET /leads/:leadId

Lead detail.

Response 200: Full lead record with activity timeline (comments, status changes, enrichment history)


PATCH /leads/:leadId

Update a lead.

Request: Partial lead fields (any subset of firstName, lastName, email, phone, company, status, score, tags, assignedTo)

Response 200: Updated lead


POST /leads/import

Bulk import leads from a CSV.

Request: multipart/form-data

file: CSV file (max 10 MB) source: string (source label to apply to all imported leads)

Response 202:

{ importId: string; total: number; status: 'processing'; }

Check import status at GET /leads/imports/:importId.


GET /leads/imports/:importId

Check the status of a bulk import.

Response 200:

{ importId: string; status: 'processing' | 'completed' | 'failed'; total: number; imported: number; skipped: number; errors: Array<{ row: number; reason: string }>; }

Content Requests

GET /blog-requests

List blog post requests.

Query params: filter.status, limit, cursor

Response 200: Paginated list:

Array<{ id: string; topic: string; targetKeyword?: string; channelId?: string; channelName?: string; status: 'draft' | 'approved' | 'in_production' | 'completed' | 'rejected'; blogPostId?: string; // set once production starts createdBy: string; createdOn: string; }>

POST /blog-requests

Submit a blog post request.

Request:

{ topic: string; targetKeyword?: string; brief?: string; channelId?: string; // WordPress channel to publish to priority?: 'normal' | 'high'; }

Response 201: Created request. If status comes back approved and there are eligible campaign periods, an activity pipeline is triggered automatically.


GET /social-requests

List social media post requests.

Query params: filter.status, filter.platform, limit, cursor


POST /social-requests

Submit a social media post request.

Request:

{ topic: string; platforms: ('linkedin' | 'facebook' | 'instagram' | 'tiktok')[]; brief?: string; scheduleFor?: string; // ISO 8601 datetime — target posting time priority?: 'normal' | 'high'; }

Response 201: Created request


Team

GET /team

List all principals (humans + agents) in the tenant.

Query params:

{ filter?: { type?: 'human' | 'agent'; status?: 'active' | 'inactive' | 'terminated'; role?: string; }; }

Response 200:

{ data: Array<{ id: string; type: 'human' | 'agent'; name: string; email?: string; role: string; // human role or agent role status: string; agentAdapter?: string; // agents only: 'claude' | 'openai' | 'ollama' runningCount?: number; // agents only: active jobs lastActiveAt?: string; createdOn: string; }>; }

POST /team/invite

Invite a new human user to the tenant.

Auth note: admin only

Request:

{ email: string; name: string; role: 'admin' | 'member' | 'reviewer'; }

Response 201:

{ inviteId: string; inviteUrl: string; expiresAt: string; }

PATCH /team/users/:userId

Update a user’s role.

Auth note: admin only

Request:

{ role: 'admin' | 'member' | 'reviewer'; }

Response 200: Updated user summary


DELETE /team/users/:userId

Remove a user from the tenant (soft delete). In-progress activities assigned to them are not affected.

Auth note: admin only

Response 204: No content


Agent Configuration (Tenant-Level)

GET /agents

List agent configurations for the tenant.

Response 200:

{ data: Array<{ id: string; role: string; status: 'active' | 'running' | 'idle' | 'error' | 'paused' | 'terminated'; runningCount: number; model: string; adapterType: string; toolNames: string[]; skillIds: string[]; maxCostUsdPerActivity?: number; concurrency: number; isGlobalDefault: boolean; // true if this config inherits from master, not customised lastErrorAt?: string; lastErrorMessage?: string; }>; }

GET /agents/:agentRole

Configuration detail for a specific agent role.

Response 200: Full agent config including adapterConfig (model, prompt template, env keys — values redacted)


PATCH /agents/:agentRole

Update tenant-level agent configuration. Only allowed fields can be overridden; sensitive model-selection changes may require admin role.

Auth note: admin only

Request:

{ model?: string; maxCostUsdPerActivity?: number; toolNames?: string[]; // full replacement list concurrency?: number; // 1–10 adapterConfig?: { promptTemplate?: string; timeoutSec?: number; maxTurnsPerRun?: number; }; }

Response 200: Updated agent config summary


POST /agents/:agentRole/test

Run the adapter health check for this agent (verifies CLI installed, API key set, model reachable).

Response 200:

{ ok: boolean; message: string; checks: Array<{ name: string; ok: boolean; detail?: string }>; }

Skills

GET /skills

List skills visible to the tenant (own skills + global platform skills).

Query params:

{ filter?: { scope?: 'tenant' | 'global'; agentRole?: string; // skills assigned to a specific agent }; }

Response 200:

{ data: Array<{ id: string; name: string; description: string; scope: 'tenant' | 'global'; assignedAgents: string[]; // agent roles sizeBytes: number; createdBy: string; createdOn: string; updatedOn: string; }>; }

POST /skills

Upload a new skill file.

Request: multipart/form-data

file: Markdown file (max 500 KB) name: string description: string agentRoles: comma-separated agent roles to assign to

Response 201: Created skill summary


PATCH /skills/:skillId

Update a skill’s content or metadata. Cannot update global skills.

Request: multipart/form-data or JSON:

{ name?: string; description?: string; agentRoles?: string[]; content?: string; // full Markdown content (JSON body only; use file upload for large content) }

Response 200: Updated skill summary


DELETE /skills/:skillId

Remove a skill from the tenant. Cannot delete global skills.

Response 204: No content


Analytics

GET /analytics/spend

Cost breakdown for the tenant.

Query params:

{ period?: 'day' | 'week' | 'month' | 'quarter'; // default 'month' startDate?: string; endDate?: string; groupBy?: 'agent' | 'model' | 'campaign' | 'deliverable_type'; }

Response 200:

{ totalUsd: number; budgetCapUsd?: number; budgetUsedPct?: number; breakdown: Array<{ label: string; // agent name, model, campaign name, etc. costUsd: number; pct: number; }>; dailySeries: Array<{ date: string; // YYYY-MM-DD costUsd: number; }>; }

GET /analytics/performance

High-level performance metrics for all goals in the current strategy.

Response 200:

{ data: Array<{ goalId: string; goalTitle: string; metric: string; targetValue: number; currentValue: number; progressPct: number; trendDirection: 'up' | 'down' | 'flat'; period: { start: string; end: string }; }>; }

GET /paid-ads/campaigns

List synced Google Ads + Meta Ads campaigns.

Response 200: Paginated list of ad campaigns with spend, impressions, clicks, and conversions


GET /paid-ads/search-terms

List search term reports for Google Ads.

Query params: filter.classification, filter.campaignId, limit, cursor

Response 200: Paginated search terms with intent classification and performance metrics


PATCH /paid-ads/search-terms/:termId/classify

Override the intent classification for a search term.

Request:

{ classification: 'high_intent' | 'low_intent' | 'negative' | 'watch'; note?: string; }

Response 200: Updated classification


Org Chart

GET /org-chart

Return the visual hierarchy of all principals (humans + agents) and their relationships for the D9 Org Chart screen.

Response 200:

{ nodes: Array<{ id: string; type: 'human' | 'agent'; name: string; role: string; status: string; reportsTo?: string; // id of parent node }>; edges: Array<{ from: string; to: string; label?: string }>; }

Reports

GET /reports

List performance reports for the tenant.

Query params:

{ filter?: { status?: 'draft' | 'ready' | 'delivered' | 'archived'; campaignId?: string; type?: 'monthly_performance' | 'quarterly_review' | 'ad_audit' | 'seo_audit' | 'custom'; startDate?: string; // period_start ≥ this date endDate?: string; }; limit?: number; // default 25 cursor?: string; }

Response 200:

{ data: Array<{ id: string; title: string; type: string; status: string; periodStart: string; // YYYY-MM-DD periodEnd: string; campaignId: string; deliveredAt: string | null; createdOn: string; }>; pagination: PaginatedResponse; }

GET /reports/:reportId

Get a single report including full Markdown content.

Response 200:

{ id: string; title: string; type: string; status: string; periodStart: string; periodEnd: string; campaignId: string; activityId: string; content: string; // Full report Markdown — fetched from MongoDB deliveredAt: string | null; deliveredBy: string | null; deliveryChannel: string | null; createdOn: string; }

Knowledge Base

All endpoints scoped to the authenticated tenant’s knowledge base.

Related: RAG Integration | RAG Architecture | Knowledge Base Screens

GET /knowledge-base/datasets

List RAG datasets for the tenant. Returns summary stats for KB1 Overview screen.

Response 200:

{ data: Array<{ id: string; name: string; description: string | null; status: 'active' | 'building' | 'error'; totalFiles: number; totalChunks: number; embeddingModel: string; allowedAgentRoles: string[]; createdOn: string; }>; }

GET /knowledge-base/datasets/:datasetId

Get dataset detail including file list. Used by KB2 Dataset File Management screen.

Response 200:

{ id: string; name: string; description: string | null; embeddingProvider: string; embeddingModel: string; vectorSize: number; chunkSize: number; chunkOverlap: number; parseType: 'NAIVE' | 'MARKDOWN' | 'MANUAL'; parserEngine: 'NODE_NATIVE' | 'DOCLING'; allowedAgentRoles: string[]; qdrantCollection: string; status: string; totalFiles: number; totalChunks: number; files: Array<{ id: string; fileName: string; mimeType: string; fileSizeBytes: number; status: 'pending' | 'parsing' | 'embedding' | 'indexed' | 'error' | 'disabled'; chunksCount: number; source: 'upload' | 'website_crawl' | 'published_content' | 'competitor_research'; enabled: boolean; errorMessage: string | null; createdOn: string; }>; }

POST /knowledge-base/datasets/:datasetId/files/upload

Upload one or more files to a dataset. Triggers async ingestion pipeline. Used by KB3 Upload Modal.

Request: multipart/form-data

{ files: File[]; // Max 20 files, max 50 MB each chunkSize?: number; // Override dataset default chunkOverlap?: number; parseType?: 'NAIVE' | 'MARKDOWN' | 'MANUAL'; parserEngine?: 'NODE_NATIVE' | 'DOCLING'; }

Response 202:

{ uploadedFiles: Array<{ id: string; fileName: string; status: 'pending'; jobId: string; // BullMQ job ID for polling }>; }

Errors:

  • 400 VALIDATION_ERROR — unsupported file type or size exceeded
  • 413 PAYLOAD_TOO_LARGE — total upload batch exceeds 200 MB

DELETE /knowledge-base/datasets/:datasetId/files/:fileId

Delete a file and all its Qdrant vectors. Irreversible.

Response 200:

{ deleted: true; chunksRemoved: number; }

PATCH /knowledge-base/datasets/:datasetId/files/:fileId

Enable/disable a file (toggles its vectors in Qdrant via enabled payload filter) or trigger re-ingestion.

Request:

{ enabled?: boolean; // Toggle visibility in RAG queries without deleting reIngest?: boolean; // Re-parse and re-embed the file (overwrites existing vectors) }

Response 200: Updated file record


GET /knowledge-base/datasets/:datasetId/files/:fileId/status

Poll ingestion status for a file. Used by KB2 progress indicator.

Response 200:

{ fileId: string; status: 'pending' | 'parsing' | 'embedding' | 'indexed' | 'error' | 'disabled'; progress: number; // 0–100 — estimated ingestion progress chunksCount: number; errorMessage: string | null; }

PATCH /knowledge-base/datasets/:datasetId/config

Update chunking/parsing configuration for a dataset. Used by KB5 Dataset Configuration screen.

Request:

{ name?: string; description?: string; chunkSize?: number; // 128–2048 chunkOverlap?: number; // 0–512, must be < chunkSize parseType?: 'NAIVE' | 'MARKDOWN' | 'MANUAL'; parserEngine?: 'NODE_NATIVE' | 'DOCLING'; allowedAgentRoles?: string[]; }

Response 200: Updated dataset

Note: Changing chunkSize, chunkOverlap, or parseType does not automatically re-index existing files. Use reIngest: true on individual files, or POST /knowledge-base/datasets/:datasetId/re-index to re-index all.


POST /knowledge-base/datasets/:datasetId/re-index

Queue all files in the dataset for re-ingestion (e.g. after changing chunk settings). Long-running — returns immediately and processes in background.

Response 202:

{ jobsQueued: number; estimatedMinutes: number; }

POST /knowledge-base/search

Run a RAG search query against one or more datasets. Powers KB4 Retrieval Sandbox.

Request:

{ query: string; datasetIds: string[]; // Which datasets to search topK?: number; // default 5, max 20 hybridAlpha?: number; // 0.0 = keyword only, 1.0 = vector only, default 0.7 minScore?: number; // Minimum relevance score filter (0–1) rerank?: boolean; // Apply cross-encoder reranking (slower, more accurate) }

Response 200:

{ results: Array<{ chunkId: string; fileId: string; fileName: string; datasetId: string; content: string; score: number; // 0–1 relevance score rankSource: 'vector' | 'keyword' | 'hybrid'; metadata: { pageNumber?: number; heading?: string; url?: string; publishedAt?: string; }; }>; searchMs: number; // Query execution time }

GET /knowledge-base/datasets/:datasetId/crawl

Get website crawl configuration for a dataset. Used by KB6 Website Crawl Settings.

Response 200:

{ datasetId: string; startUrl: string | null; maxPages: number; maxDepth: number; urlPathFilter: string | null; schedule: 'manual' | 'weekly' | 'monthly' | null; lastCrawl: { id: string; status: 'queued' | 'running' | 'completed' | 'failed'; pagesCrawled: number; pagesIndexed: number; completedAt: string | null; } | null; }

POST /knowledge-base/datasets/:datasetId/crawl

Trigger a website crawl. Used by KB6 “Crawl Now” button.

Request:

{ startUrl: string; maxPages?: number; // default 200 maxDepth?: number; // default 3 urlPathFilter?: string; // e.g. '/blog' — restrict to path prefix scheduleFor?: string; // ISO datetime — null = immediate }

Response 202:

{ crawlJobId: string; status: 'queued'; }

GET /knowledge-base/datasets/:datasetId/crawl/:crawlJobId/status

Poll a running crawl job. Used by KB6 progress display.

Response 200:

{ crawlJobId: string; status: 'queued' | 'running' | 'completed' | 'failed'; pagesCrawled: number; pagesIndexed: number; pagesFailed: number; errorMessage: string | null; completedAt: string | null; }

Chat

AI assistant chat for the tenant. Provides a conversational interface to query campaigns, activities, and reports.

POST /chat/message

Send a message to the AI assistant. Streamed response.

Request:

{ message: string; conversationId?: string; // Omit to start a new conversation context?: { campaignId?: string; // Scope the assistant to a specific campaign activityId?: string; }; }

Response: text/event-stream

event: text_delta data: {"delta":"The latest campaign shows..."} event: completed data: {"conversationId":"conv_01ABC...","messageId":"msg_01DEF...","inputTokens":512,"outputTokens":340} event: error data: {"error":"rate_limit_exceeded"}

GET /chat/conversations

List recent chat conversations for the authenticated user.

Response 200:

{ data: Array<{ id: string; title: string; // Auto-generated from first message lastMessage: string; // Truncated preview createdOn: string; updatedOn: string; }>; }

GET /chat/conversations/:conversationId

Get full conversation history.

Response 200:

{ id: string; title: string; messages: Array<{ id: string; role: 'user' | 'assistant'; content: string; createdOn: string; }>; }

DELETE /chat/conversations/:conversationId

Delete a conversation and its history.

Response 200: { deleted: true }


Integrations

API-key based third-party tool integrations (SEMrush, DataForSEO, etc.). Distinct from OAuth channels which are publishing platforms.

GET /integrations

List all integrations for the tenant.

Response 200:

{ data: Array<{ id: string; integrationType: string; name: string; status: 'connected' | 'error' | 'quota_exceeded' | 'expired'; isActive: boolean; lastVerifiedAt: string | null; errorMessage: string | null; }>; }

POST /integrations

Add a new integration.

Request:

{ integrationType: 'semrush' | 'dataforseo' | 'ahrefs' | 'google_ads_api' | 'meta_ads_api' | 'sendgrid' | 'custom'; name: string; apiKey: string; apiSecret?: string; extraConfig?: Record<string, unknown>; }

Response 201: Created integration (api_key masked)


PATCH /integrations/:integrationId

Update an integration’s credentials or name.

Response 200: Updated integration


DELETE /integrations/:integrationId

Remove an integration.

Response 200: { deleted: true }


POST /integrations/:integrationId/test

Run a health-check against the integration’s API to verify credentials.

Response 200:

{ ok: boolean; message: string; }

Audit Log

GET /audit-log

Paginated audit trail of all significant events within the tenant. Used by P13 screen.

Query params:

{ filter?: { entityType?: string; // e.g. 'activity', 'approval', 'campaign', 'user' entityId?: string; userId?: string; action?: string; // e.g. 'approved', 'rejected', 'created' startDate?: string; endDate?: string; }; limit?: number; // default 50 cursor?: string; }

Response 200:

{ data: Array<{ id: string; entityType: string; entityId: string; action: string; actorType: 'user' | 'agent' | 'system'; actorId: string; actorName: string; changes: Record<string, { from: unknown; to: unknown }> | null; ip: string | null; createdOn: string; }>; pagination: PaginatedResponse; }

Logs

GET /logs

Operational log stream — agent activity, tool calls, errors, approvals. Used by P14 screen.

Query params:

{ filter?: { level?: 'debug' | 'info' | 'warn' | 'error'; category?: 'agent' | 'tool' | 'approval' | 'system' | 'billing'; agentRole?: string; activityId?: string; startDate?: string; endDate?: string; }; limit?: number; // default 100 cursor?: string; }

Response 200:

{ data: Array<{ id: string; level: 'debug' | 'info' | 'warn' | 'error'; category: string; message: string; context: Record<string, unknown>; activityId: string | null; agentRole: string | null; createdOn: string; }>; pagination: PaginatedResponse; }

Recurring Tasks

Tenant-level view of recurring task templates. The admin app manages global templates; this API manages tenant-level configuration and variable values.

GET /recurring-tasks

List all recurring tasks active for this tenant (copies of global templates + any tenant-specific ones).

Response 200:

{ data: Array<{ id: string; name: string; description: string; assigneeType: 'agent' | 'human'; agentRole: string | null; periodicity: string; trigger: string; isActive: boolean; variables: Array<{ key: string; label: string; type: string; required: boolean }>; variableValues: Record<string, unknown>; nextRunAt: string | null; lastRunAt: string | null; }>; }

PATCH /recurring-tasks/:taskId

Update variable values or enable/disable a recurring task for this tenant.

Request:

{ isActive?: boolean; variableValues?: Record<string, unknown>; }

Response 200: Updated task


POST /recurring-tasks/:taskId/run

Manually trigger a recurring task immediately.

Response 202:

{ activityId: string; }

Events (SSE)

GET /events/stream

Subscribe to platform-wide events for the tenant. Used by the D1 Home screen for the live activity feed and real-time notifications.

Protocol: text/event-stream

Events:

event: activity_started data: {"activityId":"01ARZ...","title":"Writing blog post — Digital Marketing Trends","agentRole":"copywriter"} event: activity_completed data: {"activityId":"01ARZ...","status":"done","campaignId":"01ARZ..."} event: activity_failed data: {"activityId":"01ARZ...","error":"timeout","retryCount":2} event: approval_created data: {"approvalId":"01ARZ...","type":"content_review","riskLevel":"high","title":"Blog Post Review"} event: approval_expiring data: {"approvalId":"01ARZ...","expiresAt":"2026-04-01T09:00:00Z","hoursLeft":6} event: budget_alert data: {"type":"campaign_cap_90pct","campaignId":"01ARZ...","spentUsd":900,"capUsd":1000}

© 2026 Leadmetrics — Internal use only