Admin API
Platform super-admin API for managing tenants, global agent configuration, LLM providers, pipeline templates, and system health. Only accessible to users with role: super_admin and appAccess: manage.
Related: API Overview | Auth | Infrastructure
Base Prefix
/admin/v1Auth: Bearer token with role: super_admin and appAccess: manage
All endpoints return 403 FORBIDDEN if the calling user is not super_admin.
Tenants
GET /tenants
List all tenants on the platform.
Query params:
{
filter?: {
status?: 'active' | 'suspended' | 'cancelled' | 'trial';
plan?: 'free' | 'pro' | 'agency' | 'enterprise';
};
search?: string; // name or domain fuzzy search
limit?: number;
cursor?: string;
}Response 200: Paginated list:
Array<{
id: string;
name: string;
domain: string;
plan: string;
status: string;
monthlySpendUsd: number;
spendCapUsd: number | null;
agentCount: number;
userCount: number;
campaignCount: number;
trialEndsAt?: string;
createdOn: string;
}>POST /tenants
Create a new tenant with a primary admin user and subscription. A plan is required (either a pre-seeded planId or a custom plan via customPlanName + customRegionId). The tenant starts in onboarding status; billing does not begin until the tenant is manually activated.
Request:
{
displayName: string; // Brand/display name (required)
firstName: string; // Admin user first name (required)
lastName: string; // Admin user last name (required)
email: string; // Admin user email (required)
password: string; // Min 8 chars (required)
// Plan — one of the two groups is required
planId?: string; // ID of a pre-seeded plan
customPlanName?: string; // } together these define
customRegionId?: string; // } a new custom plan
customPrice?: number; // Price in smallest currency unit
customCycle?: 'monthly' | 'quarterly' | 'yearly';
// Tax & billing
gstin?: string;
tdsDeduction?: boolean; // default false (payWithoutTds = true when false)
billingName?: string;
billingAddress?: string;
billingCity?: string;
billingState?: string;
billingCountry?: string;
billingPostal?: string;
legalName?: string;
phone?: string;
country?: string;
}Response 201:
{
tenantId: string;
tenantName: string;
userId: string;
email: string;
invoiceId: string; // first invoice created at onboarding
invoiceNumber: string;
}Side effects:
- Creates tenant (status
onboarding), admin user, subscription, and first invoice (type: subscription,status: pending,dueAt: now + 7 days) - Tenant stays in
onboardinguntil manually activated viaPATCH /tenants/:tenantId/activate - Writes
tenant.createdaudit log entry
Errors: 400 if no plan provided · 409 if email already exists
GET /tenants/:tenantId
Full tenant detail including usage, agents, users, billing summary, and audit log.
Response 200:
{
id: string;
name: string;
domain: string;
plan: string;
status: string;
spendCapUsd: number | null;
featureFlags: Record<string, boolean>;
deploymentMode: string;
usage: {
monthlySpendUsd: number;
monthlyLlmCallCount: number;
totalActivities: number;
storageBytes: number;
};
billing: {
subscriptionId?: string;
nextBillingDate?: string;
lastPaymentUsd?: number;
lastPaymentDate?: string;
paymentStatus?: 'current' | 'overdue' | 'cancelled';
};
agents: AgentConfigSummary[];
users: UserSummary[];
createdOn: string;
}PATCH /tenants/:tenantId/activate
Activate a tenant. Transitions status from onboarding → active and stamps the billing start date on the subscription. Does not create an invoice — the first invoice was already created at tenant creation.
Request:
{ isActive: true; }Response 200:
{
tenantId: string;
status: 'active';
message: string;
}Side effects:
- Sets tenant
status = active - Updates subscription:
startDate,billingActivatedOn,billingStartDate= now;nextBillingDate= first day of next month - Billing server will generate subsequent invoices from the next cycle onwards
- Writes
tenant.activatedaudit log entry
Errors: 409 if already active · 400 if tenant has no active subscription
PATCH /tenants/:tenantId
Update tenant metadata, plan, or status.
Request:
{
name?: string;
plan?: string;
status?: 'active' | 'suspended' | 'cancelled';
spendCapUsd?: number | null;
featureFlags?: Record<string, boolean>;
}Response 200: Updated tenant summary
POST /tenants/:tenantId/suspend
Suspend a tenant. All active agent workers are paused; users cannot log in. Suspended tenants are not billed.
Request:
{ reason: string; }Response 200:
{ tenantId: string; status: 'suspended'; agentsPaused: number; }POST /tenants/:tenantId/reinstate
Reinstate a suspended tenant. Restores agent workers to their previous state.
Request:
{ note?: string; }Response 200:
{ tenantId: string; status: 'active'; agentsResumed: number; }POST /tenants/:tenantId/impersonate
Generate a short-lived impersonation token that allows the super-admin to browse the tenant’s Dashboard as a read-only observer. All actions taken during impersonation are logged to the audit trail.
Response 200:
{
impersonationToken: string; // JWT, 30 min expiry, read-only
dashboardUrl: string; // ready-to-use URL with token
}GET /tenants/:tenantId/audit-log
Paginated audit log for a specific tenant.
Query params: limit, cursor, filter.actorType (human | agent), filter.action
Response 200: Paginated audit events with action, actor, before, after, createdOn
Master Agent Configs
Global defaults applied when provisioning new tenants. Tenant-level configs override these but inherit from them on creation.
GET /agent-configs
List all master agent configurations (one per agent role).
Response 200:
{
data: Array<{
role: string;
defaultModel: string;
defaultAdapter: string;
defaultTools: string[];
defaultSkillIds: string[];
maxCostUsdPerActivity: number | null;
defaultConcurrency: number;
isAvailableOnPlans: string[]; // e.g. ['pro', 'agency', 'enterprise']
lastUpdatedOn: string;
}>;
}GET /agent-configs/:agentRole
Full master config for a specific agent role including full adapterConfig schema.
PATCH /agent-configs/:agentRole
Update a master agent configuration.
Request:
{
defaultModel?: string;
defaultTools?: string[];
defaultSkillIds?: string[];
maxCostUsdPerActivity?: number | null;
defaultConcurrency?: number;
isAvailableOnPlans?: string[];
adapterConfig?: {
promptTemplate?: string;
timeoutSec?: number;
graceSec?: number;
maxTurnsPerRun?: number;
};
}Response 200: Updated master config
POST /agent-configs/:agentRole/push
Push master config changes to tenant-level agent configs.
Request:
{
scope: 'all_tenants' | 'new_tenants_only' | 'specific_tenants';
tenantIds?: string[]; // required when scope is 'specific_tenants'
fields: string[]; // which fields to push: ['defaultModel', 'adapterConfig.timeoutSec', ...]
overrideCustom?: boolean; // default false — if true, overwrite tenant customisations
}Response 200:
{
pushed: number; // tenant configs updated
skipped: number; // tenants that had custom overrides (when overrideCustom:false)
}Global Skills
GET /skills
List global skills available to all tenants.
Query params: filter.agentRole, search, limit, cursor
Response 200: Paginated skill list (same shape as dashboard /skills with scope: 'global')
POST /skills
Upload a new global skill file.
Request: multipart/form-data
file: Markdown file (max 2 MB for global skills)
name: string
description: string
agentRoles: comma-separated agent roles (or 'all')Response 201: Created skill
PATCH /skills/:skillId
Update a global skill.
Response 200: Updated skill summary
DELETE /skills/:skillId
Remove a global skill. Tenants that have this skill assigned to their agents lose the assignment silently — it is removed from their skillIds array.
Response 204: No content
LLM Providers
GET /llm-providers
List all configured LLM provider connections and their health.
Response 200:
{
data: Array<{
id: string;
name: string; // 'anthropic', 'openai', 'ollama-local'
type: 'anthropic' | 'openai' | 'ollama' | 'custom';
status: 'healthy' | 'degraded' | 'unreachable';
models: Array<{
id: string;
name: string;
available: boolean;
availableOnPlans: string[];
}>;
rateLimitRpm?: number;
rateLimitTpm?: number;
lastCheckedAt: string;
monthlyCallCount: number;
monthlySpendUsd: number;
}>;
}POST /llm-providers
Add a new LLM provider configuration.
Request:
{
name: string;
type: 'anthropic' | 'openai' | 'ollama' | 'custom';
apiKey?: string; // stored encrypted; never returned
baseUrl?: string; // for Ollama or custom providers
models: Array<{ id: string; name: string; availableOnPlans: string[] }>;
}Response 201: Created provider (API key field omitted from response)
PATCH /llm-providers/:providerId
Update provider config or rotate API key.
Request:
{
apiKey?: string;
baseUrl?: string;
models?: Array<{ id: string; name: string; availableOnPlans: string[] }>;
status?: 'active' | 'disabled';
}Response 200: Updated provider summary
DELETE /llm-providers/:providerId
Remove a provider. All agents using models from this provider will error on next run.
Response 204: No content
Activity Templates
Pipeline templates that define the sequence of activities for each deliverable type.
GET /activity-templates
List all activity pipeline templates.
Query params:
{
filter?: {
deliverableType?: string;
status?: 'active' | 'draft' | 'archived';
};
}Response 200:
{
data: Array<{
id: string;
name: string;
deliverableType: string;
status: string;
stepCount: number;
version: number;
isDefault: boolean;
updatedOn: string;
}>;
}GET /activity-templates/:templateId
Full template detail including all steps and their agent assignments.
Response 200:
{
id: string;
name: string;
deliverableType: string;
status: string;
version: number;
isDefault: boolean;
steps: Array<{
stepNumber: number;
title: string;
agentRole: string;
type: string;
approvalRequired: boolean;
approvalType?: string;
dependsOn?: number[]; // step numbers that must complete first
estimatedCostUsd?: number;
}>;
createdBy: string;
createdOn: string;
updatedOn: string;
}POST /activity-templates
Create a new pipeline template.
Request:
{
name: string;
deliverableType: string;
steps: Array<{
stepNumber: number;
title: string;
agentRole: string;
type: string;
approvalRequired?: boolean;
approvalType?: string;
dependsOn?: number[];
}>;
setAsDefault?: boolean;
}Response 201: Created template
PATCH /activity-templates/:templateId
Update a template. Creates a new version; the previous version is preserved for audit.
Request: Partial update of name, steps, or status
Response 200: Updated template (version incremented)
POST /activity-templates/:templateId/set-default
Set this template as the default for its deliverable type. The previous default is demoted.
Response 200:
{ templateId: string; isDefault: true; previousDefaultId: string; }Recurring Task Templates
GET /recurring-templates
List all recurring task templates.
Response 200: Paginated list with cron, agentRole, status, lastRunAt, nextRunAt
POST /recurring-templates
Create a recurring task template.
Request:
{
title: string;
agentRole: string;
cron: string; // cron expression, e.g. '0 9 1 * *' (monthly at 9am)
promptTemplate: string; // supports {{variables}}
variables?: Record<string, string>;
applyToTenants: 'all' | 'plan_based' | 'specific';
tenantIds?: string[];
planIds?: string[];
}Response 201: Created template
PATCH /recurring-templates/:templateId
Update a recurring task template.
Response 200: Updated template
DELETE /recurring-templates/:templateId
Archive a recurring task template. In-progress runs are not affected.
Response 204: No content
System Health
GET /system/health
Overall platform health summary — the M8 System Health screen.
Response 200:
{
status: 'healthy' | 'degraded' | 'down';
services: {
api: 'ok' | 'error';
postgres: 'ok' | 'error';
mongo: 'ok' | 'error';
redis: 'ok' | 'error';
qdrant: 'ok' | 'error';
ollama: 'ok' | 'error';
};
queues: Array<{
name: string;
waiting: number;
active: number;
delayed: number;
failed: number;
deadLettered: number;
}>;
sseConnections: number;
lastCheckedAt: string;
}GET /system/queues
Detailed BullMQ queue stats.
Response 200: Per-queue breakdown across all tenants with (tenantId, agentRole) pairs
GET /system/errors
Recent errors across all agent runs (dead-lettered jobs).
Query params: limit, cursor, filter.tenantId, filter.agentRole
Response 200: Paginated error records with tenant context, error message, and retry history
GET /system/llm-calls
Platform-wide LLM call log for cost and usage monitoring.
Query params:
{
tenantId?: string;
model?: string;
startDate?: string;
endDate?: string;
limit?: number;
cursor?: string;
}Response 200: Paginated llm_calls records with cost and duration
Agent Runs
Cross-tenant execution log for every agent run. Powers the Execution Queue dashboard (/dashboards/execution-queue). Records are created by publishAgentEvent when workers emit agent:started / agent:completed / agent:failed.
GET /runs
List all agent runs across all tenants, newest first.
Query params:
{
tenantId?: string;
agentRole?: string;
status?: 'pending' | 'running' | 'completed' | 'failed';
startDate?: string; // ISO date
endDate?: string;
limit?: number; // default 50
cursor?: string;
}Response 200:
{
data: Array<{
runId: string;
tenantId: string;
tenantName: string;
agentRole: string;
agentName: string;
model: string | null;
status: string;
inputTokens: number | null;
outputTokens: number | null;
costUsd: number | null;
durationMs: number | null;
startedAt: string;
completedAt: string | null;
error: string | null;
}>;
nextCursor: string | null;
}GET /runs/:runId
Full detail for a single agent run including the input prompt and execution transcript.
Response 200:
{
runId: string;
tenantId: string;
tenantName: string;
agentRole: string;
agentName: string;
adapter: string | null;
model: string | null;
status: string;
inputPrompt: string | null; // full prompt sent to Claude
output: string | null; // raw LLM output text
inputSummary: string | null;
inputTokens: number | null;
outputTokens: number | null;
costUsd: number | null;
durationMs: number | null;
startedAt: string;
completedAt: string | null;
error: string | null;
skills: string[];
transcript: Array<{ // full Claude Code session transcript
type: "assistant" | "user" | "tool_use" | "tool_result" | "system" | "error";
content: string;
timestamp: number;
metadata?: Record<string, unknown>; // tool_use entries include { toolName: string }
}> | null;
agentConfig: {
name: string;
systemPrompt: string | null;
} | null;
}Note: transcript is populated for all runs after Apr 2026. Earlier runs have transcript: null.
Global Users
Platform-wide user management across all tenants. Used by M5 Global Users screen.
GET /users
List all users across all tenants.
Query params:
{
filter?: {
tenantId?: string;
role?: 'admin' | 'member' | 'reviewer';
status?: 'active' | 'suspended' | 'deactivated';
search?: string; // name or email search
};
limit?: number; // default 50
cursor?: string;
}Response 200:
{
data: Array<{
id: string;
email: string;
name: string;
role: string;
status: string;
tenantId: string;
tenantName: string;
lastLoginAt: string | null;
createdOn: string;
}>;
pagination: PaginatedResponse;
}POST /users/:userId/suspend
Suspend a user account platform-wide. Invalidates all active sessions.
Request:
{ reason: string; }Response 200: { suspended: true }
POST /users/:userId/reinstate
Reinstate a suspended user.
Response 200: { reinstated: true }
Global Invoices
Cross-tenant billing history. Used by M4 Global Invoices screen.
GET /invoices
List all billing events as invoice records across all tenants.
Query params:
{
filter?: {
tenantId?: string;
status?: 'paid' | 'pending' | 'failed' | 'refunded';
plan?: string;
startDate?: string;
endDate?: string;
};
limit?: number; // default 50
cursor?: string;
}Response 200:
{
data: Array<{
id: string;
tenantId: string;
tenantName: string;
eventType: string;
amountInr: number; // paise
amountInrFormatted: string; // "₹2,499.00"
plan: string;
razorpayEntityId: string;
status: string;
createdOn: string;
}>;
pagination: PaginatedResponse;
totals: {
totalAmountInr: number;
countPaid: number;
countFailed: number;
};
}Global Deliverables
Cross-tenant deliverable tracking. Used by M6 Global Deliverables screen.
GET /deliverables
List all deliverables across all tenants.
Query params:
{
filter?: {
tenantId?: string;
deliverableType?: string; // e.g. 'blog_posts', 'social_posts'
status?: string;
periodMonth?: string; // e.g. '2026-03'
};
limit?: number;
cursor?: string;
}Response 200:
{
data: Array<{
id: string;
tenantId: string;
tenantName: string;
deliverableType: string;
targetCount: number;
completedCount: number;
periodMonth: string;
status: string;
createdOn: string;
}>;
pagination: PaginatedResponse;
}Email Templates
Platform-wide transactional email template management. Used by M10 Email Templates screen.
GET /email-templates
List all email templates (global + any tenant overrides if tenantId filter is set).
Query params:
{
filter?: {
category?: 'approval' | 'report_delivery' | 'onboarding' | 'billing' | 'alert' | 'custom';
tenantId?: string;
};
}Response 200:
{
data: Array<{
id: string;
name: string;
slug: string;
subject: string;
category: string;
isActive: boolean;
tenantId: string | null;
sendVia: string;
updatedOn: string | null;
}>;
}GET /email-templates/:templateId
Get full template including body HTML and plain-text fallback.
Response 200: Full template record including bodyHtml, bodyText, variables
POST /email-templates
Create a new email template (or tenant override).
Request:
{
name: string;
slug: string;
subject: string;
bodyHtml: string;
bodyText: string;
variables: Array<{ key: string; description: string; required: boolean }>;
category: 'approval' | 'report_delivery' | 'onboarding' | 'billing' | 'alert' | 'custom';
tenantId?: string; // Set to create a tenant override of a global template
sendVia: 'sendgrid' | 'ses' | 'smtp';
}Response 201: Created template
PATCH /email-templates/:templateId
Update an existing template.
Response 200: Updated template
DELETE /email-templates/:templateId
Delete a template. Cannot delete a global template if tenant overrides exist.
Response 200: { deleted: true }
POST /email-templates/:templateId/preview
Send a test email using this template with sample variable values.
Request:
{
to: string; // Email address to send preview to
sampleVariables: Record<string, string>;
}Response 200: { sent: true; messageId: string }
Blog Request Queue
Cross-tenant management of blog requests and the Blog AI Agent queue. Used by M12–M14 screens.
GET /blog-requests
List all blog requests across all tenants.
Query params:
{
filter?: {
tenantId?: string;
status?: 'pending' | 'approved' | 'in_progress' | 'published' | 'rejected';
assignee?: 'ai' | 'human' | 'unassigned';
};
limit?: number; // default 50
cursor?: string;
}Response 200:
{
data: Array<{
id: string;
tenantId: string;
tenantName: string;
title: string;
status: string;
requestedBy: string;
assignee: 'ai' | 'human' | 'unassigned';
createdOn: string;
dueDate: string | null;
}>;
pagination: PaginatedResponse;
}GET /blog-requests/:requestId
Get full detail for a single blog request. Used by M13 Request Detail screen.
Response 200: Full request including content brief, assignment history, and linked activity
PATCH /blog-requests/:requestId
Update a blog request (reassign, change status, add notes).
Request:
{
status?: string;
assignee?: 'ai' | 'human';
notes?: string;
}Response 200: Updated request
GET /blogs
List all blog posts across all tenants across all stages (request → draft → published). Used by M14 All Blogs screen.
Query params:
{
filter?: {
tenantId?: string;
status?: 'draft' | 'approved' | 'published' | 'archived';
startDate?: string;
endDate?: string;
};
limit?: number; // default 50
cursor?: string;
}Response 200:
{
data: Array<{
id: string;
tenantId: string;
tenantName: string;
title: string;
status: string;
wordCount: number | null;
publishedAt: string | null;
createdOn: string;
}>;
pagination: PaginatedResponse;
}Billing
GET /billing/events
List billing events (Razorpay webhooks received).
Query params: tenantId, filter.eventType, startDate, endDate, limit, cursor
Response 200: Paginated billing events
POST /billing/webhook
Razorpay webhook receiver. Validates Razorpay signature (X-Razorpay-Signature header), stores the event, and triggers tenant plan updates or payment status changes.
Auth required: Razorpay webhook signature (not a Bearer token)
Request: Raw Razorpay webhook payload
Response 200:
{ received: true; }Note: This endpoint is not protected by the standard Bearer auth middleware — it uses Razorpay HMAC signature validation instead. It is mounted at the application level before auth middleware.
POST /tenants/:tenantId/billing/override
Manually override a tenant’s billing state (e.g. extend trial, apply discount, fix a failed payment).
Request:
{
action: 'extend_trial' | 'apply_discount' | 'mark_paid' | 'change_plan';
value?: string | number; // plan name for change_plan; discount % for apply_discount; days for extend_trial
reason: string; // required for audit log
}Response 200:
{ tenantId: string; action: string; applied: boolean; }