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/v1Auth: 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 infailedstatus422 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 withwakeReason: 'review_approved'rejected— all linked activities are re-enqueued withwakeReason: 'review_feedback'and the noterevision_requested— same as rejected but withreviewerFeedbackcontext
Errors:
409 CONFLICT— approval is not inpendingstatus422 UNPROCESSABLE—selectedOptionis required but missing400 VALIDATION_ERROR—noteis 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 toResponse 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 };
}>;
}Paid Ads
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 exceeded413 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}