Skip to Content
APIAdmin API

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/v1

Auth: 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 onboarding until manually activated via PATCH /tenants/:tenantId/activate
  • Writes tenant.created audit 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 onboardingactive 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.activated audit 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; }

© 2026 Leadmetrics — Internal use only